diff --git a/Gateway/Http/Client/TransactionCancel.php b/Gateway/Http/Client/TransactionCancel.php index f7d1406469..f4ab3e0e8d 100644 --- a/Gateway/Http/Client/TransactionCancel.php +++ b/Gateway/Http/Client/TransactionCancel.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2023 Adyen N.V. (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -22,27 +22,14 @@ class TransactionCancel implements ClientInterface { - /** - * @var Data - */ - private Data $adyenHelper; - - /** - * @var Idempotency - */ - private Idempotency $idempotencyHelper; - /** * @param Data $adyenHelper * @param Idempotency $idempotencyHelper */ public function __construct( - Data $adyenHelper, - Idempotency $idempotencyHelper - ) { - $this->adyenHelper = $adyenHelper; - $this->idempotencyHelper = $idempotencyHelper; - } + private readonly Data $adyenHelper, + private readonly Idempotency $idempotencyHelper + ) { } /** * @param TransferInterface $transferObject @@ -58,7 +45,7 @@ public function placeRequest(TransferInterface $transferObject): array $client = $this->adyenHelper->initializeAdyenClientWithClientConfig($clientConfig); $service = $this->adyenHelper->initializeModificationsApi($client); - $responseData = []; + $responseCollection = []; foreach ($requests as $request) { $idempotencyKey = $this->idempotencyHelper->generateIdempotencyKey( @@ -83,8 +70,10 @@ public function placeRequest(TransferInterface $transferObject): array $responseData['error'] = $e->getMessage(); $this->adyenHelper->logAdyenException($e); } + + $responseCollection[] = $responseData; } - return $responseData; + return $responseCollection; } } diff --git a/Gateway/Http/Client/TransactionCapture.php b/Gateway/Http/Client/TransactionCapture.php index 05a71cbd73..d63e03074e 100644 --- a/Gateway/Http/Client/TransactionCapture.php +++ b/Gateway/Http/Client/TransactionCapture.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2023 Adyen N.V. (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -14,38 +14,19 @@ use Adyen\AdyenException; use Adyen\Client; use Adyen\Model\Checkout\PaymentCaptureRequest; -use Adyen\Payment\Api\Data\OrderPaymentInterface; +use Adyen\Payment\Gateway\Validator\AbstractModificationsResponseValidator; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\Idempotency; -use Adyen\Payment\Helper\Requests; use Adyen\Payment\Logger\AdyenLogger; -use Adyen\Service\Checkout\ModificationsApi; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Payment\Gateway\Http\ClientInterface; use Magento\Payment\Gateway\Http\TransferInterface; class TransactionCapture implements ClientInterface { - const MULTIPLE_AUTHORIZATIONS = 'multiple_authorizations'; - const FORMATTED_CAPTURE_AMOUNT = 'formatted_capture_amount'; - const CAPTURE_AMOUNT = 'capture_amount'; + const CAPTURE_AMOUNT = 'amount'; + const CAPTURE_VALUE = 'value'; const ORIGINAL_REFERENCE = 'paymentPspReference'; - const CAPTURE_RECEIVED = 'received'; - - /** - * @var Data - */ - private Data $adyenHelper; - - /** - * @var AdyenLogger - */ - private AdyenLogger $adyenLogger; - - /** - * @var Idempotency - */ - private Idempotency $idempotencyHelper; /** * @param Data $adyenHelper @@ -53,14 +34,10 @@ class TransactionCapture implements ClientInterface * @param Idempotency $idempotencyHelper */ public function __construct( - Data $adyenHelper, - AdyenLogger $adyenLogger, - Idempotency $idempotencyHelper - ) { - $this->adyenHelper = $adyenHelper; - $this->adyenLogger = $adyenLogger; - $this->idempotencyHelper = $idempotencyHelper; - } + private readonly Data $adyenHelper, + private readonly AdyenLogger $adyenLogger, + private readonly Idempotency $idempotencyHelper + ) { } /** * @param TransferInterface $transferObject @@ -70,66 +47,16 @@ public function __construct( */ public function placeRequest(TransferInterface $transferObject): array { - $request = $transferObject->getBody(); + $requestCollection = $transferObject->getBody(); $headers = $transferObject->getHeaders(); $clientConfig = $transferObject->getClientConfig(); $client = $this->adyenHelper->initializeAdyenClientWithClientConfig($clientConfig); $service = $this->adyenHelper->initializeModificationsApi($client); - $requestOptions['headers'] = $headers; - $request['applicationInfo'] = $this->adyenHelper->buildApplicationInfo($client); - - if (array_key_exists(self::MULTIPLE_AUTHORIZATIONS, $request)) { - return $this->placeMultipleCaptureRequests($service, $request, $requestOptions); - } - - $idempotencyKeyExtraData = $request['idempotencyExtraData']; - unset($request['idempotencyExtraData']); - - $idempotencyKey = $this->idempotencyHelper->generateIdempotencyKey( - $request, - $idempotencyKeyExtraData ?? null - ); - $requestOptions['idempotencyKey'] = $idempotencyKey; - - $this->adyenHelper->logRequest($request, Client::API_CHECKOUT_VERSION, '/captures'); - $paymentCaptureRequest = new PaymentCaptureRequest($request); - $responseData = []; - - try { - $response = $service->captureAuthorisedPayment( - $request['paymentPspReference'], - $paymentCaptureRequest, - $requestOptions - ); + $responseCollection = []; - $responseData = $response->toArray(); - $responseData = $this->copyParamsToResponse($responseData, $request); - $this->adyenHelper->logResponse($responseData); - } catch (AdyenException $e) { - $this->adyenHelper->logAdyenException($e); - $responseData['error'] = $e->getMessage(); - } - - return $responseData; - } - - /** - * @param ModificationsApi $service - * @param array $requestContainer - * @param array $requestOptions - * @return array - */ - private function placeMultipleCaptureRequests( - ModificationsApi $service, - array $requestContainer, - array $requestOptions - ): array { - $response = []; - $applicationInfo = $requestContainer['applicationInfo']; - - foreach ($requestContainer[self::MULTIPLE_AUTHORIZATIONS] as $request) { + foreach ($requestCollection as $request) { $idempotencyKeyExtraData = $request['idempotencyExtraData']; unset($request['idempotencyExtraData']); $idempotencyKey = $this->idempotencyHelper->generateIdempotencyKey( @@ -137,42 +64,44 @@ private function placeMultipleCaptureRequests( $idempotencyKeyExtraData ?? null ); + $request['applicationInfo'] = $this->adyenHelper->buildApplicationInfo($client); + $requestOptions['headers'] = $headers; $requestOptions['idempotencyKey'] = $idempotencyKey; + + $paymentPspReference = $request['paymentPspReference']; + unset($request['paymentPspReference']); + try { - // Copy merchant account from parent array to every request array - $request[Requests::MERCHANT_ACCOUNT] = $requestContainer[Requests::MERCHANT_ACCOUNT]; - $request['applicationInfo'] = $applicationInfo; $paymentCaptureRequest = new PaymentCaptureRequest($request); - $singleResponseObj = $service->captureAuthorisedPayment( - $request['paymentPspReference'], + $this->adyenHelper->logRequest($request, Client::API_CHECKOUT_VERSION, '/captures'); + + $response = $service->captureAuthorisedPayment( + $paymentPspReference, $paymentCaptureRequest, $requestOptions ); - $singleResponse = $singleResponseObj->toArray(); - $singleResponse[self::FORMATTED_CAPTURE_AMOUNT] = $request['amount']['currency'] . ' ' . - $this->adyenHelper->originalAmount( - $request['amount']['value'], - $request['amount']['currency'] - ); - $singleResponse = $this->copyParamsToResponse($singleResponse, $request); - $response[self::MULTIPLE_AUTHORIZATIONS][] = $singleResponse; - } catch (AdyenException $e) { - $pspReference = $request[OrderPaymentInterface::PSPREFRENCE] ?? 'pspReference not set'; + $responseData = $response->toArray(); + $this->adyenHelper->logResponse($responseData); + $responseCollection[] = $this->copyParamsToResponse($responseData, $request); + } catch (AdyenException $e) { $message = sprintf( - 'Exception occurred when attempting to capture multiple authorizations. - Authorization with pspReference %s: %s', - $pspReference, + "An error occurred during the capture attempt%s. %s", + !empty($paymentPspReference) ? + ' of authorisation with pspreference ' . $paymentPspReference : + '', $e->getMessage() ); + $responseData['error'] = $message; + $this->adyenLogger->error($message); - $response[self::MULTIPLE_AUTHORIZATIONS]['error'] = $message; + $responseCollection[] = $responseData; } } - return $response; + return $responseCollection; } /** @@ -182,8 +111,16 @@ private function placeMultipleCaptureRequests( */ private function copyParamsToResponse(array $response, array $request): array { - $response[self::CAPTURE_AMOUNT] = $request['amount']['value']; - $response[self::ORIGINAL_REFERENCE] = $request[self::ORIGINAL_REFERENCE]; + $originalAmount = $this->adyenHelper->originalAmount( + $request['amount']['value'], + $request['amount']['currency'] + ); + + $response[AbstractModificationsResponseValidator::FORMATTED_MODIFICATIONS_AMOUNT] = sprintf( + "%s %s", + $request['amount']['currency'], + $originalAmount + ); return $response; } diff --git a/Gateway/Http/Client/TransactionPayment.php b/Gateway/Http/Client/TransactionPayment.php index 1561c31fb5..841fcfebc4 100644 --- a/Gateway/Http/Client/TransactionPayment.php +++ b/Gateway/Http/Client/TransactionPayment.php @@ -120,7 +120,6 @@ public function placeRequest(TransferInterface $transferObject): array $client = $this->adyenHelper->initializeAdyenClientWithClientConfig($clientConfig); $service = $this->adyenHelper->initializePaymentsApi($client); $responseCollection = []; - $responseCollection['hasOnlyGiftCards'] = false; try { list($requestData, $giftcardResponseCollection) = $this->processGiftcards($requestData, $service); @@ -130,7 +129,6 @@ public function placeRequest(TransferInterface $transferObject): array $responseCollection = array_merge($responseCollection, $giftcardResponseCollection); if ($this->remainingOrderAmount === 0) { - $responseCollection['hasOnlyGiftCards'] = true; return $responseCollection; } } diff --git a/Gateway/Http/Client/TransactionRefundInterface.php b/Gateway/Http/Client/TransactionRefundInterface.php index 2a77478cc5..9bc4732527 100644 --- a/Gateway/Http/Client/TransactionRefundInterface.php +++ b/Gateway/Http/Client/TransactionRefundInterface.php @@ -9,4 +9,5 @@ interface TransactionRefundInterface extends ClientInterface const REFUND_AMOUNT = 'refund_amount'; const REFUND_CURRENCY = 'refund_currency'; const ORIGINAL_REFERENCE = 'original_reference'; + const PSPREFERENCE = 'pspReference'; } diff --git a/Gateway/Request/CancelDataBuilder.php b/Gateway/Request/CancelDataBuilder.php index 9aca8481ad..45f76fa45b 100644 --- a/Gateway/Request/CancelDataBuilder.php +++ b/Gateway/Request/CancelDataBuilder.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2015 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -16,31 +16,20 @@ use Magento\Payment\Gateway\Request\BuilderInterface; use Adyen\Payment\Model\ResourceModel\Order\Payment; use Adyen\Payment\Helper\Data; -use Adyen\Payment\Helper\Config; /** * Class CustomerDataBuilder */ class CancelDataBuilder implements BuilderInterface { - /** @var Payment $adyenPaymentResourceModel */ - private $adyenPaymentResourceModel; - - /** @var Config $configHelper */ - private $configHelper; - - /** @var Data $adyenHelper */ - private $adyenHelper; - + /** + * @param Payment $adyenPaymentResourceModel + * @param Data $adyenHelper + */ public function __construct( - Payment $adyenPaymentResourceModel, - Data $adyenHelper, - Config $configHelper - ){ - $this->adyenPaymentResourceModel = $adyenPaymentResourceModel; - $this->adyenHelper = $adyenHelper; - $this->configHelper = $configHelper; - } + private readonly Payment $adyenPaymentResourceModel, + private readonly Data $adyenHelper + ){ } /** * Create cancel_or_refund request diff --git a/Gateway/Request/CaptureDataBuilder.php b/Gateway/Request/CaptureDataBuilder.php index 0a7671b9e4..a91f42e6d5 100644 --- a/Gateway/Request/CaptureDataBuilder.php +++ b/Gateway/Request/CaptureDataBuilder.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2021 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -13,20 +13,19 @@ use Adyen\AdyenException; use Adyen\Payment\Api\Data\OrderPaymentInterface; -use Adyen\Payment\Gateway\Http\Client\TransactionCapture; use Adyen\Payment\Helper\AdyenOrderPayment; use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Data as DataHelper; use Adyen\Payment\Helper\OpenInvoice; use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; -use Adyen\Payment\Model\ResourceModel\Order\Payment; +use Adyen\Payment\Model\ResourceModel\Order\Payment as AdyenOrderPaymentResourceModel; use Magento\Framework\App\Action\Context; use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; -use Magento\Sales\Model\Order; +use Magento\Payment\Model\InfoInterface; use Magento\Sales\Model\Order\Invoice; /** @@ -40,7 +39,7 @@ class CaptureDataBuilder implements BuilderInterface * @param AdyenOrderPayment $adyenOrderPaymentHelper * @param AdyenLogger $adyenLogger * @param Context $context - * @param Payment $orderPaymentResourceModel + * @param AdyenOrderPaymentResourceModel $orderPaymentResourceModel * @param OpenInvoice $openInvoiceHelper * @param PaymentMethods $paymentMethodsHelper */ @@ -50,7 +49,7 @@ public function __construct( private readonly AdyenOrderPayment $adyenOrderPaymentHelper, private readonly AdyenLogger $adyenLogger, private readonly Context $context, - private readonly Payment $orderPaymentResourceModel, + private readonly AdyenOrderPaymentResourceModel $orderPaymentResourceModel, protected readonly OpenInvoice $openInvoiceHelper, private readonly PaymentMethods $paymentMethodsHelper ) { } @@ -63,12 +62,12 @@ public function build(array $buildSubject): array /** @var PaymentDataObject $paymentDataObject */ $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); - $paymentMethodInstance = $payment->getMethodInstance(); - /** @var Order $order */ $order = $payment->getOrder(); + /** @var Invoice $latestInvoice */ $latestInvoice = $order->getInvoiceCollection()->getLastItem(); $invoiceAmountCurrency = $this->chargedCurrency->getInvoiceAmountCurrency($latestInvoice); + $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order); $currency = $invoiceAmountCurrency->getCurrencyCode(); $amount = $this->adyenHelper->formatAmount($invoiceAmountCurrency->getAmount(), $currency); @@ -90,52 +89,68 @@ public function build(array $buildSubject): array throw new AdyenException($errorMessage); } - $adyenOrderPayments = $this->orderPaymentResourceModel->getLinkedAdyenOrderPayments($payment->getEntityId()); + $requestCollection = []; + $adyenOrderPayments = $this->orderPaymentResourceModel->getLinkedAdyenOrderPayments( + $payment->getEntityId() + ); + // If the full amount won't be captured OR there are multiple payments to capture if (!empty($adyenOrderPayments) && ($amount < $orderAmountCents || count($adyenOrderPayments) > 1)) { - return $this->buildPartialOrMultipleCaptureData( + $requestCollection = $this->buildPartialOrMultipleCaptureData( + $latestInvoice, $payment, + $amount, $currency, - $adyenOrderPayments, - $invoiceAmountCurrency->getAmount() + $adyenOrderPayments + ); + } else { + $requestCollection[] = $this->buildCaptureRequestPayload( + $latestInvoice, + $payment, + $amount, + $currency, + $pspReference, + $payment->getOrder()->getTotalInvoiced() ?? 0 ); } - $modificationAmount = ['value' => $amount, 'currency' => $currency]; - $requestBody = [ - "amount" => $modificationAmount, - "reference" => $payment->getOrder()->getIncrementId(), - "paymentPspReference" => $pspReference, - "idempotencyExtraData" => [ - 'totalInvoiced' => $payment->getOrder()->getTotalInvoiced() ?? 0 + return [ + 'body' => $requestCollection, + 'clientConfig' => [ + 'storeId' => $payment->getOrder()->getStoreId() ] ]; - - if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) { - $openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForInvoice($latestInvoice); - $requestBody = array_merge($requestBody, $openInvoiceFields); - } - $request['body'] = $requestBody; - $request['clientConfig'] = ["storeId" => $payment->getOrder()->getStoreId()]; - - return $request; } /** * Return the data of the multiple capture requests required to capture the full amount OR * multiple capture requests required to capture a partial amount OR * a single capture request required to capture a partial amount + * + * @param Invoice $latestInvoice + * @param InfoInterface $payment + * @param int $captureAmountCents + * @param string $currency + * @param array $adyenOrderPayments + * @return array + * @throws LocalizedException */ - public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrderPayments, $captureAmount): array - { - $this->adyenLogger->addAdyenInfoLog(sprintf( - 'Building PARTIAL capture request for multiple authorisations, on payment %s', - $payment->getId() - ), $this->adyenLogger->getOrderContext($payment->getOrder())); - - $captureAmountCents = $this->adyenHelper->formatAmount($captureAmount, $currency); - $paymentMethodInstance = $payment->getMethodInstance(); - $captureData = []; + private function buildPartialOrMultipleCaptureData( + Invoice $latestInvoice, + InfoInterface $payment, + int $captureAmountCents, + string $currency, + array $adyenOrderPayments + ): array { + $this->adyenLogger->addAdyenInfoLog( + sprintf( + 'Building PARTIAL capture request for multiple authorisations, on payment %s', + $payment->getId() + ), + $this->adyenLogger->getOrderContext($payment->getOrder()) + ); + + $requestCollection = []; $counterAmount = 0; $i = 0; @@ -147,6 +162,7 @@ public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrd $paymentAmount - $totalCaptured, $currency ); + // If there is still some amount available to capture if ($availableAmountToCaptureCents > 0) { // IF the counter amount + available amount to capture from @@ -160,43 +176,72 @@ public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrd $counterAmount += $amountCents; - $modificationAmount = [ - 'currency' => $currency, - 'value' => $amountCents - ]; - $authToCapture = [ - "amount" => $modificationAmount, - "reference" => $payment->getOrder()->getIncrementId(), - "paymentPspReference" => $adyenOrderPayment[OrderPaymentInterface::PSPREFRENCE] - ]; - - if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) { - $order = $payment->getOrder(); - $invoices = $order->getInvoiceCollection(); - // The latest invoice will contain only the selected items(and quantities) for the (partial) capture - /** @var Invoice $invoice */ - $invoice = $invoices->getLastItem(); - - $openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForInvoice($invoice); - $authToCapture = array_merge($authToCapture, $openInvoiceFields); - } - $authToCapture['idempotencyExtraData'] = [ - 'totalInvoiced' => $adyenOrderPayment[OrderPaymentInterface::TOTAL_CAPTURED] ?? 0, - 'originalPspReference' => $adyenOrderPayment[OrderPaymentInterface::PSPREFRENCE] - ] ; + $payload = $this->buildCaptureRequestPayload( + $latestInvoice, + $payment, + $amountCents, + $currency, + $adyenOrderPayment[OrderPaymentInterface::PSPREFRENCE], + $adyenOrderPayment[OrderPaymentInterface::TOTAL_CAPTURED] ?? 0 + ); - $captureData[] = $authToCapture; + $requestCollection[] = $payload; } + $i++; } - $requestBody = [ - TransactionCapture::MULTIPLE_AUTHORIZATIONS => $captureData + return $requestCollection; + } + + /** + * @param Invoice $latestInvoice + * @param InfoInterface $payment + * @param int $amount + * @param string $currency + * @param string $pspReference + * @param float|null $totalCaptured + * @return array + * @throws LocalizedException + */ + private function buildCaptureRequestPayload( + Invoice $latestInvoice, + InfoInterface $payment, + int $amount, + string $currency, + string $pspReference, + float $totalCaptured = null + ): array { + $method = $payment->getMethod(); + $storeId = $payment->getOrder()->getStoreId(); + + if (isset($method) && $method === 'adyen_moto') { + $merchantAccount = $payment->getAdditionalInformation('motoMerchantAccount'); + } else { + $merchantAccount = $this->adyenHelper->getAdyenMerchantAccount($method, $storeId); + } + + $payload = [ + 'merchantAccount' => $merchantAccount, + 'amount' => [ + 'value' => $amount, + 'currency' => $currency + ], + 'reference' => $payment->getOrder()->getIncrementId(), + 'paymentPspReference' => $pspReference, + 'idempotencyExtraData' => [ + 'totalInvoiced' => $totalCaptured, + 'originalPspReference' => $pspReference + ] ]; - $request['body'] = $requestBody; - $request['clientConfig'] = ["storeId" => $payment->getOrder()->getStoreId()]; + // Build line items + $paymentMethodInstance = $payment->getMethodInstance(); + if ($this->paymentMethodsHelper->getRequiresLineItems($paymentMethodInstance)) { + $openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForInvoice($latestInvoice); + $payload = array_merge($payload, $openInvoiceFields); + } - return $request; + return $payload; } } diff --git a/Gateway/Response/AbstractOrderStatusHistoryHandler.php b/Gateway/Response/AbstractOrderStatusHistoryHandler.php new file mode 100644 index 0000000000..a3b8083cce --- /dev/null +++ b/Gateway/Response/AbstractOrderStatusHistoryHandler.php @@ -0,0 +1,59 @@ + + */ + +namespace Adyen\Payment\Gateway\Response; + +use Adyen\AdyenException; +use Adyen\Payment\Helper\OrderStatusHistory; +use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; + +class AbstractOrderStatusHistoryHandler implements HandlerInterface +{ + /** + * @param string $eventType Indicates the API call event type (authorization, capture etc.) + * @param OrderStatusHistory $orderStatusHistoryHelper + * @throws AdyenException + */ + public function __construct( + protected readonly string $eventType, + protected readonly OrderStatusHistory $orderStatusHistoryHelper + ) { + if (empty($eventType)) { + throw new AdyenException( + __('Order status history can not be handled due to missing constructor argument!') + ); + } + } + + /** + * @throws AdyenException + */ + public function handle(array $handlingSubject, array $responseCollection): void + { + $readPayment = SubjectReader::readPayment($handlingSubject); + $payment = $readPayment->getPayment(); + $order = $payment->getOrder(); + + foreach ($responseCollection as $response) { + $comment = $this->orderStatusHistoryHelper->buildApiResponseComment($response, $this->getEventType()); + $order->addCommentToStatusHistory($comment, $order->getStatus()); + } + } + + /** + * @return string + */ + private function getEventType(): string + { + return $this->eventType; + } +} diff --git a/Gateway/Response/CheckoutPaymentCommentHistoryHandler.php b/Gateway/Response/CheckoutPaymentCommentHistoryHandler.php deleted file mode 100644 index 1fc333259b..0000000000 --- a/Gateway/Response/CheckoutPaymentCommentHistoryHandler.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Magento\Payment\Gateway\Helper\SubjectReader; -use Magento\Payment\Gateway\Response\HandlerInterface; - -class CheckoutPaymentCommentHistoryHandler implements HandlerInterface -{ - /** - * @param array $handlingSubject - * @param array $responseCollection - * @return $this - */ - public function handle(array $handlingSubject, array $responseCollection): self - { - $readPayment = SubjectReader::readPayment($handlingSubject); - $payment = $readPayment->getPayment(); - $comment = __("Adyen Result response:"); - - foreach ($responseCollection as $response) { - $responseCode = $response['resultCode'] ?? $response['response'] ?? ''; - - if (!empty($responseCode)) { - $comment .= '
' . __('authResult:') . ' ' . $responseCode; - $payment->getOrder()->setAdyenResulturlEventCode($responseCode); - } - - if (isset($response['pspReference'])) { - $comment .= '
' . __('pspReference:') . ' ' . $response['pspReference']; - } - $comment .= '
'; - } - - $payment->getOrder()->addStatusHistoryComment($comment, $payment->getOrder()->getStatus()); - return $this; - } -} diff --git a/Gateway/Response/PayByLinkResponseHandler.php b/Gateway/Response/CheckoutPaymentLinksResponseHandler.php similarity index 69% rename from Gateway/Response/PayByLinkResponseHandler.php rename to Gateway/Response/CheckoutPaymentLinksResponseHandler.php index 2d89154516..a0156153f1 100644 --- a/Gateway/Response/PayByLinkResponseHandler.php +++ b/Gateway/Response/CheckoutPaymentLinksResponseHandler.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2021 Adyen NV (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -13,36 +13,38 @@ use Adyen\Payment\Helper\Data; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; +use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; -class PayByLinkResponseHandler implements HandlerInterface +class CheckoutPaymentLinksResponseHandler implements HandlerInterface { /** - * @var Data + * @param Data $adyenHelper */ - protected $adyenHelper; - public function __construct( - Data $adyenHelper - ) { - $this->adyenHelper = $adyenHelper; - } + protected readonly Data $adyenHelper + ) { } /** * @param array $handlingSubject * @param array $response + * @return void + * @throws LocalizedException */ - public function handle(array $handlingSubject, array $response) + public function handle(array $handlingSubject, array $response): void { $paymentDataObject = SubjectReader::readPayment($handlingSubject); - + /** @var Payment $payment */ $payment = $paymentDataObject->getPayment(); - // Set transaction to pending by default, wait for notification. - // This must match the status checked for expired orders/payments in - // Adyen\Payment\Cron\Providers\PayByLinkExpiredPaymentOrdersProvider::provide + // Set the transaction not to processing by default wait for notification $payment->setIsTransactionPending(true); + // Do not close the transaction, so you can do a cancel() and void + $payment->setIsTransactionClosed(false); + // Do not close the parent transaction + $payment->setShouldCloseParentTransaction(false); if (!empty($response['url'])) { $payment->setAdditionalInformation(AdyenPayByLinkConfigProvider::URL_KEY, $response['url']); @@ -55,9 +57,5 @@ public function handle(array $handlingSubject, array $response) if (!empty($response['id'])) { $payment->setAdditionalInformation(AdyenPayByLinkConfigProvider::ID_KEY, $response['id']); } - - // do not close transaction so you can do a cancel() and void - $payment->setIsTransactionClosed(false); - $payment->setShouldCloseParentTransaction(false); } } diff --git a/Gateway/Response/CheckoutPaymentsDetailsHandler.php b/Gateway/Response/CheckoutPaymentsDetailsHandler.php deleted file mode 100644 index 1731b7db15..0000000000 --- a/Gateway/Response/CheckoutPaymentsDetailsHandler.php +++ /dev/null @@ -1,76 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Adyen\Payment\Helper\Data; -use Magento\Payment\Gateway\Helper\SubjectReader; -use Magento\Payment\Gateway\Response\HandlerInterface; -use Magento\Sales\Model\Order\Payment; - -class CheckoutPaymentsDetailsHandler implements HandlerInterface -{ - /** @var Data */ - protected $adyenHelper; - const ADYEN_BOLETO = 'adyen_boleto'; - - public function __construct( - Data $adyenHelper - ) { - $this->adyenHelper = $adyenHelper; - } - - /** - * This is being used for all checkout methods (adyen hpp payment method) - * - */ - public function handle(array $handlingSubject, array $responseCollection) - { - $paymentDataObject = SubjectReader::readPayment($handlingSubject); - - /** @var Payment $payment */ - $payment = $paymentDataObject->getPayment(); - - // set transaction not to processing by default wait for notification - $payment->setIsTransactionPending(true); - - // Email sending is set at CheckoutDataBuilder for Boleto - // Otherwise, we don't want to send a confirmation email - if ($payment->getMethod() != self::ADYEN_BOLETO) { - $payment->getOrder()->setCanSendNewEmailFlag(false); - } - - // for partial payments, non-giftcard payments will always be the last element in the collection - // for non-partial, there is only one response in the collection - $response = end($responseCollection); - if (!empty($response['pspReference'])) { - // set pspReference as transactionId - $payment->setCcTransId($response['pspReference']); - $payment->setLastTransId($response['pspReference']); - - //set CC Type - $ccType = $payment->getAdditionalInformation('cc_type'); - - if (!empty($response['additionalData']['paymentMethod']) && $ccType == null) { - $ccType = $response['additionalData']['paymentMethod']; - $payment->setAdditionalInformation('cc_type', $ccType); - $payment->setCcType($ccType); - } - - // set transaction - $payment->setTransactionId($response['pspReference']); - } - - // do not close transaction, so you can do a cancel() and void - $payment->setIsTransactionClosed(false); - $payment->setShouldCloseParentTransaction(false); - } -} diff --git a/Gateway/Response/CheckoutPaymentsResponseHandler.php b/Gateway/Response/CheckoutPaymentsResponseHandler.php new file mode 100644 index 0000000000..cfe8365087 --- /dev/null +++ b/Gateway/Response/CheckoutPaymentsResponseHandler.php @@ -0,0 +1,89 @@ + + */ + +namespace Adyen\Payment\Gateway\Response; + +use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Helper\Vault; +use Magento\Framework\Exception\LocalizedException; +use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; + +class CheckoutPaymentsResponseHandler implements HandlerInterface +{ + public function __construct( + private readonly Vault $vaultHelper + ) { } + + /** + * @param array $handlingSubject + * @param array $responseCollection + * @return void + * @throws LocalizedException + */ + public function handle(array $handlingSubject, array $responseCollection): void + { + // Always get the last item in the response collection and ignore the partial payments + $response = end($responseCollection); + + $paymentDataObject = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ + $payment = $paymentDataObject->getPayment(); + + $payment->getOrder()->setAdyenResulturlEventCode($response['resultCode']); + $payment->setAdditionalInformation('resultCode', $response['resultCode']); + $payment->setAdditionalInformation('3dActive', false); + + // Set the transaction not to processing by default wait for notification + $payment->setIsTransactionPending(true); + // Do not close the transaction, so you can do a cancel() and void + $payment->setIsTransactionClosed(false); + // Do not close the parent transaction + $payment->setShouldCloseParentTransaction(false); + + if (!empty($response['pspReference'])) { + $payment->setCcTransId($response['pspReference']); + $payment->setLastTransId($response['pspReference']); + $payment->setTransactionId($response['pspReference']); + $payment->setAdditionalInformation('pspReference', $response['pspReference']); + } + + if ($payment->getMethod() === PaymentMethods::ADYEN_CC && + !empty($response['additionalData']['paymentMethod']) && + $payment->getCcType() == null) { + $payment->setCcType($response['additionalData']['paymentMethod']); + } + + if (!empty($response['action'])) { + $payment->setAdditionalInformation('action', $response['action']); + } + + if (!empty($response['additionalData'])) { + $payment->setAdditionalInformation('additionalData', $response['additionalData']); + } + + if (!empty($response['details'])) { + $payment->setAdditionalInformation('details', $response['details']); + } + + if (!empty($response['donationToken'])) { + $payment->setAdditionalInformation('donationToken', $response['donationToken']); + } + + // Handle recurring payment details + $this->vaultHelper->handlePaymentResponseRecurringDetails($payment, $response); + + if ($payment->getMethod() != PaymentMethods::ADYEN_BOLETO) { + $payment->getOrder()->setCanSendNewEmailFlag(false); + } + } +} diff --git a/Gateway/Response/StateDataHandler.php b/Gateway/Response/CheckoutStateDataCleanupHandler.php similarity index 52% rename from Gateway/Response/StateDataHandler.php rename to Gateway/Response/CheckoutStateDataCleanupHandler.php index 74c25d8969..102f3e1f6d 100644 --- a/Gateway/Response/StateDataHandler.php +++ b/Gateway/Response/CheckoutStateDataCleanupHandler.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2022 Adyen N.V. (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -13,42 +13,42 @@ use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\ResourceModel\StateData; -use Adyen\Payment\Model\ResourceModel\StateData\Collection; +use Adyen\Payment\Model\ResourceModel\StateData\Collection as StateDataCollection; use Exception; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; -class StateDataHandler implements HandlerInterface +class CheckoutStateDataCleanupHandler implements HandlerInterface { - private Collection $adyenStateData; - private StateData $stateDataResourceModel; - private AdyenLogger $adyenLogger; - + /** + * @param StateDataCollection $adyenStateDataCollection + * @param StateData $stateDataResourceModel + * @param AdyenLogger $adyenLogger + */ public function __construct( - Collection $adyenStateData, - StateData $stateDataResourceModel, - AdyenLogger $adyenLogger - ) { - $this->adyenStateData = $adyenStateData; - $this->stateDataResourceModel = $stateDataResourceModel; - $this->adyenLogger = $adyenLogger; - } - - public function handle(array $handlingSubject, array $response): self + private readonly StateDataCollection $adyenStateDataCollection, + private readonly StateData $stateDataResourceModel, + private readonly AdyenLogger $adyenLogger + ) { } + + /** + * @param array $handlingSubject + * @param array $response + * @return void + */ + public function handle(array $handlingSubject, array $response): void { $readPayment = SubjectReader::readPayment($handlingSubject); $quoteId = $readPayment->getPayment()->getOrder()->getQuoteId(); - $stateDataCollection = $this->adyenStateData->getStateDataRowsWithQuoteId($quoteId); + $stateDataCollection = $this->adyenStateDataCollection->getStateDataRowsWithQuoteId($quoteId); - foreach ($stateDataCollection->getIterator() as $stateDataItem) { + foreach ($stateDataCollection as $stateDataItem) { try { $this->stateDataResourceModel->delete($stateDataItem); } catch (Exception $exception) { $this->adyenLogger->error(__("State data was not cleaned-up: %s", $exception->getMessage())); } } - - return $this; } } diff --git a/Gateway/Response/PaymentCancelDetailsHandler.php b/Gateway/Response/ModificationsCancelsResponseHandler.php similarity index 53% rename from Gateway/Response/PaymentCancelDetailsHandler.php rename to Gateway/Response/ModificationsCancelsResponseHandler.php index 33d4132808..8a7277bfe4 100644 --- a/Gateway/Response/PaymentCancelDetailsHandler.php +++ b/Gateway/Response/ModificationsCancelsResponseHandler.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2015 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -11,23 +11,26 @@ namespace Adyen\Payment\Gateway\Response; +use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; -class PaymentCancelDetailsHandler implements HandlerInterface +class ModificationsCancelsResponseHandler implements HandlerInterface { /** * @param array $handlingSubject - * @param array $response + * @param array $responseCollection + * @return void */ - public function handle(array $handlingSubject, array $response) + public function handle(array $handlingSubject, array $responseCollection): void { - $payment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); - - /** @var OrderPaymentInterface $payment */ + $payment = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ $payment = $payment->getPayment(); - // set pspReference as lastTransId only! - $payment->setLastTransId($response['pspReference']); + foreach ($responseCollection as $response) { + $payment->setLastTransId($response['pspReference']); + } // close transaction because you have cancelled the transaction $payment->setIsTransactionClosed(true); diff --git a/Gateway/Response/ModificationsCapturesResponseHandler.php b/Gateway/Response/ModificationsCapturesResponseHandler.php new file mode 100644 index 0000000000..5e27d94765 --- /dev/null +++ b/Gateway/Response/ModificationsCapturesResponseHandler.php @@ -0,0 +1,67 @@ + + */ + +namespace Adyen\Payment\Gateway\Response; + +use Adyen\Payment\Gateway\Http\Client\TransactionCapture; +use Adyen\Payment\Helper\Invoice; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; + +class ModificationsCapturesResponseHandler implements HandlerInterface +{ + /** + * @param AdyenLogger $adyenLogger + * @param Invoice $invoiceHelper + */ + public function __construct( + private readonly AdyenLogger $adyenLogger, + private readonly Invoice $invoiceHelper + ) { } + + /** + * @param array $handlingSubject + * @param array $responseCollection + * @throws AlreadyExistsException + */ + public function handle(array $handlingSubject, array $responseCollection): void + { + $payment = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ + $payment = $payment->getPayment(); + + foreach ($responseCollection as $response) { + if (count($responseCollection) > 1) { + $this->adyenLogger->info(sprintf( + 'Handling partial or multiple capture response for order %s', + $payment->getOrder()->getIncrementId() + )); + } + + $payment->setLastTransId($response['pspReference']); + + $this->invoiceHelper->createAdyenInvoice( + $payment, + $response['pspReference'] ?? '', + $response[TransactionCapture::ORIGINAL_REFERENCE], + $response[TransactionCapture::CAPTURE_AMOUNT][TransactionCapture::CAPTURE_VALUE] + ); + } + + // Set the invoice status to pending and wait for CAPTURE webhook + $payment->setIsTransactionPending(true); + // Do not close parent authorisation since order can still be cancelled/refunded + $payment->setShouldCloseParentTransaction(false); + } +} diff --git a/Gateway/Response/ModificationsRefundsResponseHandler.php b/Gateway/Response/ModificationsRefundsResponseHandler.php new file mode 100644 index 0000000000..8886844ed8 --- /dev/null +++ b/Gateway/Response/ModificationsRefundsResponseHandler.php @@ -0,0 +1,75 @@ + + */ + +namespace Adyen\Payment\Gateway\Response; + +use Adyen\Payment\Gateway\Http\Client\TransactionRefundInterface as TransactionRefund; +use Adyen\Payment\Helper\Creditmemo; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\AlreadyExistsException; +use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; + +class ModificationsRefundsResponseHandler implements HandlerInterface +{ + /** + * @param Creditmemo $creditmemoHelper + * @param Data $adyenDataHelper + * @param AdyenLogger $adyenLogger + */ + public function __construct( + private readonly Creditmemo $creditmemoHelper, + private readonly Data $adyenDataHelper, + private readonly AdyenLogger $adyenLogger + ) { } + + /** + * @param array $handlingSubject + * @param array $responseCollection + * @throws AlreadyExistsException + */ + public function handle(array $handlingSubject, array $responseCollection): void + { + $payment = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ + $payment = $payment->getPayment(); + + foreach ($responseCollection as $response) { + if (count($responseCollection) > 1) { + $this->adyenLogger->info(sprintf( + 'Handling partial or multiple refund response for order %s', + $payment->getOrder()->getIncrementId() + )); + } + + $payment->setLastTransId($response[TransactionRefund::PSPREFERENCE]); + + $this->creditmemoHelper->createAdyenCreditMemo( + $payment, + $response[TransactionRefund::PSPREFERENCE], + $response[TransactionRefund::ORIGINAL_REFERENCE], + $this->adyenDataHelper->originalAmount( + $response[TransactionRefund::REFUND_AMOUNT], + $response[TransactionRefund::REFUND_CURRENCY] + ) + ); + } + + /** + * close current transaction because you have refunded the goods + * but only on full refund close the authorisation + */ + $payment->setIsTransactionClosed(true); + $payment->setShouldCloseParentTransaction(!$payment->getCreditmemo()->getInvoice()->canRefund()); + } +} diff --git a/Gateway/Response/PaymentAuthorisationDetailsHandler.php b/Gateway/Response/PaymentAuthorisationDetailsHandler.php deleted file mode 100644 index 13e73e894e..0000000000 --- a/Gateway/Response/PaymentAuthorisationDetailsHandler.php +++ /dev/null @@ -1,53 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Magento\Payment\Gateway\Helper\SubjectReader; -use Magento\Payment\Gateway\Response\HandlerInterface; -use Magento\Sales\Api\Data\OrderPaymentInterface; - -class PaymentAuthorisationDetailsHandler implements HandlerInterface -{ - /** - * @param array $handlingSubject - * @param array $responseCollection - */ - public function handle(array $handlingSubject, array $responseCollection): void - { - $payment = SubjectReader::readPayment($handlingSubject); - - /** @var OrderPaymentInterface $payment */ - $payment = $payment->getPayment(); - - // set transaction not to processing by default wait for notification - $payment->setIsTransactionPending(true); - - // no not send order confirmation mail - $payment->getOrder()->setCanSendNewEmailFlag(false); - - // for partial payments, non-giftcard payments will always be the last element in the collection - // for non-partial, there is only one response in the collection - $response = end($responseCollection); - if (!empty($response['pspReference'])) { - // set pspReference as transactionId - $payment->setCcTransId($response['pspReference']); - $payment->setLastTransId($response['pspReference']); - - // set transaction - $payment->setTransactionId($response['pspReference']); - } - - // do not close transaction so you can do a cancel() and void - $payment->setIsTransactionClosed(false); - $payment->setShouldCloseParentTransaction(false); - } -} diff --git a/Gateway/Response/PaymentCaptureDetailsHandler.php b/Gateway/Response/PaymentCaptureDetailsHandler.php deleted file mode 100644 index 8a01992eb8..0000000000 --- a/Gateway/Response/PaymentCaptureDetailsHandler.php +++ /dev/null @@ -1,130 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Adyen\Payment\Gateway\Http\Client\TransactionCapture; -use Adyen\Payment\Helper\Invoice; -use Adyen\Payment\Logger\AdyenLogger; -use Magento\Framework\Exception\AlreadyExistsException; -use Magento\Payment\Gateway\Response\HandlerInterface; -use Magento\Sales\Model\Order; - -class PaymentCaptureDetailsHandler implements HandlerInterface -{ - /** @var AdyenLogger $adyenLogger */ - private $adyenLogger; - - /** @var Invoice $invoiceHelper */ - private $invoiceHelper; - - /** - * PaymentCaptureDetailsHandler constructor. - * @param AdyenLogger $adyenLogger - * @param Invoice $invoiceHelper - */ - public function __construct(AdyenLogger $adyenLogger, Invoice $invoiceHelper) - { - $this->adyenLogger = $adyenLogger; - $this->invoiceHelper = $invoiceHelper; - } - - /** - * @param array $handlingSubject - * @param array $response - * @throws AlreadyExistsException - */ - public function handle(array $handlingSubject, array $response) - { - $payment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); - - /** @var Order\Payment $payment */ - $payment = $payment->getPayment(); - - if (array_key_exists(TransactionCapture::MULTIPLE_AUTHORIZATIONS, $response)) { - $this->handlePartialOrMultipleCaptureRequests($payment, $response); - } else { - // set pspReference as lastTransId only! - $payment->setLastTransId($response['pspReference']); - $this->invoiceHelper->createAdyenInvoice( - $payment, - $response['pspReference'], - $response[TransactionCapture::ORIGINAL_REFERENCE], - $response[TransactionCapture::CAPTURE_AMOUNT] - ); - - // The capture request will return a capture-received message, but it doesn't mean the capture has been final - // so the invoice is set to Pending - if ($response["status"] === TransactionCapture::CAPTURE_RECEIVED) { - $this->setInvoiceToPending($payment); - } - } - } - - /** - * @param Order\Payment $payment - * @param $responseContainer - * @throws AlreadyExistsException - */ - public function handlePartialOrMultipleCaptureRequests(Order\Payment $payment, $responseContainer) - { - $lastTransId = null; - $this->adyenLogger->info(sprintf( - 'Handling partial OR multiple capture response in details handler for order %s', - $payment->getOrder()->getIncrementId() - )); - - $captureNotReceived = []; - - foreach ($responseContainer[TransactionCapture::MULTIPLE_AUTHORIZATIONS] as $response) { - if ($response["status"] !== TransactionCapture::CAPTURE_RECEIVED) { - $captureNotReceived[] = $response['pspReference']; - } else { - $lastTransId = $response['pspReference']; - $this->invoiceHelper->createAdyenInvoice( - $payment, - $response['pspReference'], - $response[TransactionCapture::ORIGINAL_REFERENCE], - $response[TransactionCapture::CAPTURE_AMOUNT] - ); - } - } - - if (isset($lastTransId)) { - // Set transId to the last capture request - $payment->setLastTransId($lastTransId); - } - - if (empty($captureNotReceived)) { - $this->setInvoiceToPending($payment); - } else { - $this->adyenLogger->error(sprintf( - 'Response for transactions [%s] did not contain [capture-received]', - implode(', ', $captureNotReceived) - )); - } - } - - /** - * Set payment to pending to ensure that the invoice is created in an OPEN state - * - * @param $payment - * @return mixed - */ - private function setInvoiceToPending($payment) - { - $payment->setIsTransactionPending(true); - // Do not close parent authorisation since order can still be cancelled/refunded - $payment->setShouldCloseParentTransaction(false); - - return $payment; - } -} diff --git a/Gateway/Response/PaymentCommentHistoryHandler.php b/Gateway/Response/PaymentCommentHistoryHandler.php deleted file mode 100644 index 1bce11c9fa..0000000000 --- a/Gateway/Response/PaymentCommentHistoryHandler.php +++ /dev/null @@ -1,147 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Adyen\Payment\Gateway\Http\Client\TransactionCapture; -use Adyen\Payment\Logger\AdyenLogger; -use Magento\Payment\Gateway\Response\HandlerInterface; - -class PaymentCommentHistoryHandler implements HandlerInterface -{ - /** @var AdyenLogger $adyenLogger */ - private $adyenLogger; - - /** - * PaymentCaptureDetailsHandler constructor. - * @param AdyenLogger $adyenLogger - */ - public function __construct(AdyenLogger $adyenLogger) - { - $this->adyenLogger = $adyenLogger; - } - - /** - * @param array $handlingSubject - * @param array $response - * @return $this - */ - public function handle(array $handlingSubject, array $response) - { - $readPayment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); - - $payment = $readPayment->getPayment(); - - if (array_key_exists(TransactionCapture::MULTIPLE_AUTHORIZATIONS, $response)) { - return $this->handlePartialOrMultipleCaptureRequests($payment, $response); - } - - $responseCode = $this->getResponseCode($response); - $pspReference = $this->getPspReference($response); - - $type = 'Adyen Result response:'; - $comment = __( - '%1
authResult: %2
pspReference: %3 ', - $type, - $responseCode, - $pspReference - ); - - if ($responseCode) { - $payment->getOrder()->setAdyenResulturlEventCode($responseCode); - } - - $payment->getOrder()->addStatusHistoryComment($comment, $payment->getOrder()->getStatus()); - - return $this; - } - - /** - * Handle multiple capture requests by creating a comment for each request, and adding all the event codes - * in the order model - * - * @param $payment - * @param array $responseContainer - * @return $this - */ - private function handlePartialOrMultipleCaptureRequests($payment, array $responseContainer) - { - $this->adyenLogger->info(sprintf( - 'Handling partial OR multiple capture response in comment history handler for order %s', - $payment->getOrder()->getIncrementId() - )); - - $resultEventCodes = []; - foreach ($responseContainer[TransactionCapture::MULTIPLE_AUTHORIZATIONS] as $response) { - $responseCode = $this->getResponseCode($response); - $pspReference = $this->getPspReference($response); - $amount = $response[TransactionCapture::FORMATTED_CAPTURE_AMOUNT]; - - $type = 'Adyen Result response:'; - $comment = __( - '%1
authResult: %2
pspReference: %3
amount: %4 ', - $type, - $responseCode, - $pspReference, - $amount - ); - - $resultEventCodes[] = $responseCode; - - $payment->getOrder()->addStatusHistoryComment($comment, $payment->getOrder()->getStatus()); - } - - if (!empty($resultEventCodes)) { - $payment->getOrder()->setAdyenResulturlEventCode(implode(', ', $resultEventCodes)); - } - - return $this; - } - - /** - * Search for the response code in the passed response array - * - * @param $response - * @return string - */ - private function getResponseCode($response) - { - if (isset($response['resultCode'])) { - $responseCode = $response['resultCode']; - } else { - // try to get response from response key (used for modifications - if (isset($response['response'])) { - $responseCode = $response['response']; - } else { - $responseCode = ''; - } - } - - return $responseCode; - } - - /** - * Get the pspReference or return empty string if not found - * - * @param $response - * @return string - */ - private function getPspReference($response) - { - if (isset($response['pspReference'])) { - $pspReference = $response['pspReference']; - } else { - $pspReference = ''; - } - - return $pspReference; - } -} diff --git a/Gateway/Response/PaymentCommentHistoryRefundHandler.php b/Gateway/Response/PaymentCommentHistoryRefundHandler.php deleted file mode 100644 index f388872f0a..0000000000 --- a/Gateway/Response/PaymentCommentHistoryRefundHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Magento\Payment\Gateway\Response\HandlerInterface; - -class PaymentCommentHistoryRefundHandler implements HandlerInterface -{ - /** - * @param array $handlingSubject - * @param array $response - * @return $this - */ - public function handle(array $handlingSubject, array $response) - { - $readPayment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); - - $payment = $readPayment->getPayment(); - - foreach ($response as $singleResponse) { - if (isset($singleResponse['resultCode'])) { - $responseCode = $singleResponse['resultCode']; - } else { - // try to get response from response key (used for modifications - if (isset($singleResponse['response'])) { - $responseCode = $singleResponse['response']; - } else { - $responseCode = ""; - } - } - - if (isset($singleResponse['pspReference'])) { - $pspReference = $singleResponse['pspReference']; - } else { - $pspReference = ""; - } - - $type = 'Adyen Result response:'; - $comment = __( - '%1
authResult: %2
pspReference: %3 ', - $type, - $responseCode, - $pspReference - ); - - if ($responseCode) { - $payment->getOrder()->setAdyenResulturlEventCode($responseCode); - } - - $payment->getOrder()->addStatusHistoryComment($comment, $payment->getOrder()->getStatus()); - } - - return $this; - } -} diff --git a/Gateway/Response/PaymentRefundDetailsHandler.php b/Gateway/Response/PaymentRefundDetailsHandler.php deleted file mode 100644 index 4575ac218c..0000000000 --- a/Gateway/Response/PaymentRefundDetailsHandler.php +++ /dev/null @@ -1,81 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Adyen\Payment\Gateway\Http\Client\TransactionRefundInterface as TransactionRefund; -use Adyen\Payment\Helper\Creditmemo; -use Adyen\Payment\Helper\Data; -use Magento\Framework\Exception\AlreadyExistsException; -use Magento\Framework\Exception\LocalizedException; -use Magento\Payment\Gateway\Response\HandlerInterface; - -class PaymentRefundDetailsHandler implements HandlerInterface -{ - /** - * @var Creditmemo - */ - private $creditmemoHelper; - - /** @var Data */ - private $adyenDataHelper; - - public function __construct( - Creditmemo $creditmemoHelper, - Data $adyenDataHelper - ) { - $this->creditmemoHelper = $creditmemoHelper; - $this->adyenDataHelper = $adyenDataHelper; - } - - /** - * @param array $handlingSubject - * @param array $response - * @throws AlreadyExistsException|LocalizedException - */ - public function handle(array $handlingSubject, array $response) - { - $payment = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); - - /** @var Order\Payment $payment */ - $payment = $payment->getPayment(); - - foreach ($response as $singleResponse) { - if (isset($singleResponse['error'])) { - throw new LocalizedException( - "The refund failed. Please make sure the amount is not greater than the limit or negative. - Otherwise, refer to the logs for details." - ); - } - - // set pspReference as lastTransId only! - $payment->setLastTransId($singleResponse['pspReference']); - - $this->creditmemoHelper->createAdyenCreditMemo( - $payment, - $singleResponse['pspReference'], - $singleResponse[TransactionRefund::ORIGINAL_REFERENCE], - $this->adyenDataHelper->originalAmount( - $singleResponse[TransactionRefund::REFUND_AMOUNT], - $singleResponse[TransactionRefund::REFUND_CURRENCY] - ) - ); - } - - /** - * close current transaction because you have refunded the goods - * but only on full refund close the authorisation - */ - $payment->setIsTransactionClosed(true); - $closeParent = !(bool)$payment->getCreditmemo()->getInvoice()->canRefund(); - $payment->setShouldCloseParentTransaction($closeParent); - } -} diff --git a/Gateway/Response/VaultDetailsHandler.php b/Gateway/Response/VaultDetailsHandler.php deleted file mode 100644 index 7b34b5ae5b..0000000000 --- a/Gateway/Response/VaultDetailsHandler.php +++ /dev/null @@ -1,59 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Response; - -use Adyen\Payment\Helper\Vault; -use Magento\Framework\Exception\LocalizedException; -use Magento\Payment\Gateway\Data\PaymentDataObject; -use Magento\Payment\Gateway\Helper\SubjectReader; -use Magento\Payment\Gateway\Response\HandlerInterface; -use Magento\Sales\Model\Order\Payment; - -class VaultDetailsHandler implements HandlerInterface -{ - /** - * @var Vault - */ - private Vault $vaultHelper; - - /** - * @param Vault $vaultHelper - */ - public function __construct(Vault $vaultHelper) - { - $this->vaultHelper = $vaultHelper; - } - - /** - * @param array $handlingSubject - * @param array $responseCollection - * @return void - */ - public function handle(array $handlingSubject, array $responseCollection): void - { - // for (non-) partial payments, the non-giftcard payment is always last. - $response = end($responseCollection); - - // payments without additional data or only giftcards should be ignored. - if (empty($response['additionalData']) || $responseCollection['hasOnlyGiftCards']) { - return; - } - - /** @var PaymentDataObject $orderPayment */ - $orderPayment = SubjectReader::readPayment($handlingSubject); - /** @var Payment $payment */ - $payment = $orderPayment->getPayment(); - - // Handle recurring details - $this->vaultHelper->handlePaymentResponseRecurringDetails($payment, $response); - } -} diff --git a/Gateway/Validator/AbstractModificationsResponseValidator.php b/Gateway/Validator/AbstractModificationsResponseValidator.php new file mode 100644 index 0000000000..3dbd0385bf --- /dev/null +++ b/Gateway/Validator/AbstractModificationsResponseValidator.php @@ -0,0 +1,150 @@ + + */ + +namespace Adyen\Payment\Gateway\Validator; + +use Adyen\AdyenException; +use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\ValidatorException; +use Magento\Payment\Gateway\Helper\SubjectReader; +use Magento\Payment\Gateway\Validator\AbstractValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; + +class AbstractModificationsResponseValidator extends AbstractValidator +{ + const FORMATTED_MODIFICATIONS_AMOUNT = 'formattedModificationAmount'; + const REQUIRED_RESPONSE_FIELDS = [ + 'status', + 'pspReference' + ]; + + /** + * @param ResultInterfaceFactory $resultFactory + * @param AdyenLogger $adyenLogger + * @param string $modificationType + * @param array $validStatuses + * @throws AdyenException + */ + public function __construct( + ResultInterfaceFactory $resultFactory, + private readonly AdyenLogger $adyenLogger, + private readonly string $modificationType, + private readonly array $validStatuses + ) { + if (empty($modificationType) || empty($validStatuses)) { + throw new AdyenException( + __('Modification response can not be handled due to missing constructor arguments!') + ); + } + + parent::__construct($resultFactory); + } + + /** + * @param array $validationSubject + * @return ResultInterface + * @throws ValidatorException + */ + public function validate(array $validationSubject): ResultInterface + { + $responseCollection = SubjectReader::readResponse($validationSubject); + + if (empty($responseCollection)) { + throw new ValidatorException(__("No responses were provided")); + } + + $errorMessages = []; + + foreach ($responseCollection as $response) { + $errorMessages = array_merge($errorMessages, $this->validateRequiredFields($response)); + $errorMessages = array_merge($errorMessages, $this->validateResponseStatus($response)); + } + + return $this->createResult(empty($errorMessages), $errorMessages); + } + + /** + * @param array $response + * @return array + */ + private function validateRequiredFields(array $response): array + { + $errorMessages = []; + + foreach (self::REQUIRED_RESPONSE_FIELDS as $requiredField) { + if (!array_key_exists($requiredField, $response)) { + $missingFieldErrorMessage = __( + '%1 field is missing in %2 response.', + $requiredField, + $this->getModificationType() + ); + + $this->adyenLogger->error($missingFieldErrorMessage); + + $errorMessages[] = $missingFieldErrorMessage; + } + } + + return $errorMessages; + } + + /** + * @param array $response + * @return array + */ + private function validateResponseStatus(array $response): array + { + $errorMessages = []; + + if (!in_array($response['status'], $this->getValidStatuses())) { + $errorMessage = __( + 'An error occurred while validating the %1 response', + $this->getModificationType() + ); + + $logMessage = sprintf( + "An error occurred while validating the %s response%s. %s", + $this->getModificationType(), + isset($response[self::FORMATTED_MODIFICATIONS_AMOUNT]) ? + ' with amount ' . + $response[self::FORMATTED_MODIFICATIONS_AMOUNT] : '', + $response['error'] ?? '' + ); + + $this->adyenLogger->error($logMessage); + + $errorMessages[] = $errorMessage; + } + + return $errorMessages; + } + + /** + * Returns the modification type of the child class defined in di.xml + * + * @return string + */ + private function getModificationType(): string + { + return $this->modificationType; + } + + /** + * Returns the valid statuses of the child class defined in di.xml + * + * @return array + */ + private function getValidStatuses(): array + { + return $this->validStatuses; + } +} diff --git a/Gateway/Validator/CancelResponseValidator.php b/Gateway/Validator/CancelResponseValidator.php deleted file mode 100644 index 83840bc343..0000000000 --- a/Gateway/Validator/CancelResponseValidator.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Validator; - -use Magento\Payment\Gateway\Validator\AbstractValidator; - -class CancelResponseValidator extends AbstractValidator -{ - /** - * @var \Adyen\Payment\Logger\AdyenLogger - */ - private $adyenLogger; - - /** - * CancelResponseValidator constructor. - * - * @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory - * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger - */ - public function __construct( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory, - \Adyen\Payment\Logger\AdyenLogger $adyenLogger - ) { - $this->adyenLogger = $adyenLogger; - parent::__construct($resultFactory); - } - - /** - * @param array $validationSubject - * @return \Magento\Payment\Gateway\Validator\ResultInterface - */ - public function validate(array $validationSubject) - { - $response = \Magento\Payment\Gateway\Helper\SubjectReader::readResponse($validationSubject); - - $isValid = true; - $errorMessages = []; - - // The available response code that the API can return in case of a successful cancellation - $expectedResponses = ['received']; - - if (empty($response['status']) || !in_array($response['status'], $expectedResponses)) { - $errorMsg = __('Error with cancellation'); - $this->adyenLogger->error($errorMsg); - - if (!empty($response['error'])) { - $this->adyenLogger->error($response['error']); - } - - $errorMessages[] = $errorMsg; - $isValid = false; - } - - return $this->createResult($isValid, $errorMessages); - } -} diff --git a/Gateway/Validator/CaptureResponseValidator.php b/Gateway/Validator/CaptureResponseValidator.php deleted file mode 100644 index dff6986bbb..0000000000 --- a/Gateway/Validator/CaptureResponseValidator.php +++ /dev/null @@ -1,102 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Validator; - -use Adyen\Payment\Gateway\Http\Client\TransactionCapture; -use Magento\Payment\Gateway\Validator\AbstractValidator; - -class CaptureResponseValidator extends AbstractValidator -{ - /** - * @var \Adyen\Payment\Logger\AdyenLogger - */ - private $adyenLogger; - - /** - * CaptureResponseValidator constructor. - * - * @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory - * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger - */ - public function __construct( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory, - \Adyen\Payment\Logger\AdyenLogger $adyenLogger - ) { - $this->adyenLogger = $adyenLogger; - parent::__construct($resultFactory); - } - - /** - * @param array $validationSubject - * @return \Magento\Payment\Gateway\Validator\ResultInterface - */ - public function validate(array $validationSubject) - { - $response = \Magento\Payment\Gateway\Helper\SubjectReader::readResponse($validationSubject); - - $isValid = true; - $errorMessages = []; - - if (array_key_exists(TransactionCapture::MULTIPLE_AUTHORIZATIONS, $response)) { - return $this->validateMultipleCaptureRequests($response); - } - - if (empty($response['status']) || $response['status'] != TransactionCapture::CAPTURE_RECEIVED) { - $errorMessages[] = $this->buildErrorMessages($response); - $isValid = false; - } - - return $this->createResult($isValid, $errorMessages); - } - - /** - * Validate a response which contains multiple capture responses - * - * @param $responseContainer - * @return \Magento\Payment\Gateway\Validator\ResultInterface - */ - public function validateMultipleCaptureRequests($responseContainer) - { - $isValid = true; - $errorMessages = []; - - foreach ($responseContainer[TransactionCapture::MULTIPLE_AUTHORIZATIONS] as $response) { - if (empty($response['status']) || $response['status'] != TransactionCapture::CAPTURE_RECEIVED) { - $errorMessages[] = $this->buildErrorMessages($response, true); - } - } - - return $this->createResult($isValid, $errorMessages); - } - - /** - * @param $response - * @param bool $multiple - * @return \Magento\Framework\Phrase|string - */ - private function buildErrorMessages($response, bool $multiple = false) - { - if ($multiple && array_key_exists(TransactionCapture::FORMATTED_CAPTURE_AMOUNT, $response)) { - $errorMsg = __('Error with capture on transaction with amount') . $response[TransactionCapture::FORMATTED_CAPTURE_AMOUNT]; - } else { - $errorMsg = __('Error with capture'); - } - - $this->adyenLogger->error($errorMsg); - - if (!empty($response['error'])) { - $this->adyenLogger->error($response['error']); - } - - return $errorMsg; - } -} diff --git a/Gateway/Validator/CheckoutResponseValidator.php b/Gateway/Validator/CheckoutResponseValidator.php index b2e59bbc5a..b07d4d4abe 100644 --- a/Gateway/Validator/CheckoutResponseValidator.php +++ b/Gateway/Validator/CheckoutResponseValidator.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2015 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -11,7 +11,8 @@ namespace Adyen\Payment\Gateway\Validator; -use Adyen\Payment\Helper\Data; +use Adyen\Model\Checkout\PaymentResponse; +use Adyen\Payment\Exception\AbstractAdyenException; use Adyen\Payment\Logger\AdyenLogger; use Magento\Framework\Exception\ValidatorException; use Magento\Payment\Gateway\Helper\SubjectReader; @@ -21,61 +22,46 @@ class CheckoutResponseValidator extends AbstractValidator { - /** - * @var AdyenLogger - */ - private $adyenLogger; - - /** - * @var Data - */ - private $adyenHelper; - - /** - * @var Array - */ - const ALLOWED_ERROR_CODES = ['124']; + const VALID_RESULT_CODES = [ + PaymentResponse::RESULT_CODE_AUTHORISED, + PaymentResponse::RESULT_CODE_RECEIVED, + PaymentResponse::RESULT_CODE_IDENTIFY_SHOPPER, + PaymentResponse::RESULT_CODE_CHALLENGE_SHOPPER, + PaymentResponse::RESULT_CODE_PRESENT_TO_SHOPPER, + PaymentResponse::RESULT_CODE_PENDING, + PaymentResponse::RESULT_CODE_REDIRECT_SHOPPER + ]; /** - * CheckoutResponseValidator constructor. - * * @param ResultInterfaceFactory $resultFactory * @param AdyenLogger $adyenLogger - * @param Data $adyenHelper */ public function __construct( ResultInterfaceFactory $resultFactory, - AdyenLogger $adyenLogger, - Data $adyenHelper + private readonly AdyenLogger $adyenLogger ) { - $this->adyenLogger = $adyenLogger; - $this->adyenHelper = $adyenHelper; parent::__construct($resultFactory); } /** * @param array $validationSubject * @return ResultInterface + * @throws ValidatorException */ public function validate(array $validationSubject): ResultInterface { - // Extract all the payment responses - $responseCollection = $validationSubject['response']; - unset($validationSubject['response']); - - // hasOnlyGiftCards is needed later but cannot be processed as a response - unset($responseCollection['hasOnlyGiftCards']); - - // Assign the remaining items to $commandSubject - $commandSubject = $validationSubject; + $responseCollection = SubjectReader::readResponse($validationSubject); if (empty($responseCollection)) { throw new ValidatorException(__("No responses were provided")); } - foreach ($responseCollection as $thisResponse) { - $responseSubject = array_merge($commandSubject, ['response' => $thisResponse]); - $this->validateResponse($responseSubject); + foreach ($responseCollection as $response) { + if (empty($response['resultCode'])) { + $this->handleEmptyResultCode($response); + } else { + $this->validateResultCode($response['resultCode']); + } } return $this->createResult(true); @@ -84,90 +70,30 @@ public function validate(array $validationSubject): ResultInterface /** * @throws ValidatorException */ - private function validateResponse($responseSubject): void - { - $response = SubjectReader::readResponse($responseSubject); - $paymentDataObjectInterface = SubjectReader::readPayment($responseSubject); - $payment = $paymentDataObjectInterface->getPayment(); - - $payment->setAdditionalInformation('3dActive', false); - - // Handle empty result for unexpected cases - if (empty($response['resultCode'])) { - $this->handleEmptyResultCode($response); - } - - // Handle the `/payments` response - $this->validateResult($response, $payment); - } - - /** - * @throws ValidatorException - */ - private function validateResult($response, $payment) + private function validateResultCode(string $resultCode): void { - $resultCode = $response['resultCode']; - $payment->setAdditionalInformation('resultCode', $resultCode); - - if (!empty($response['action'])) { - $payment->setAdditionalInformation('action', $response['action']); - } - - if (!empty($response['additionalData'])) { - $payment->setAdditionalInformation('additionalData', $response['additionalData']); - } - - if (!empty($response['pspReference'])) { - $payment->setAdditionalInformation('pspReference', $response['pspReference']); - } - - if (!empty($response['details'])) { - $payment->setAdditionalInformation('details', $response['details']); - } - - if (!empty($response['donationToken'])) { - $payment->setAdditionalInformation('donationToken', $response['donationToken']); - } - - switch ($resultCode) { - case "Authorised": - case "Received": - // Save cc_type if available in the response - if (!empty($response['additionalData']['paymentMethod'])) { - $ccType = $this->adyenHelper->getMagentoCreditCartType( - $response['additionalData']['paymentMethod'] - ); - $payment->setAdditionalInformation('cc_type', $ccType); - $payment->setCcType($ccType); - } - break; - case "IdentifyShopper": - case "ChallengeShopper": - case "PresentToShopper": - case 'Pending': - case "RedirectShopper": - // nothing extra - break; - case "Refused": - $errorMsg = __('The payment is REFUSED.'); - // this will result the specific error - throw new ValidatorException($errorMsg); - default: - $errorMsg = __('Error with payment method please select different payment method.'); - throw new ValidatorException($errorMsg); + if (strcmp($resultCode, PaymentResponse::RESULT_CODE_REFUSED) === 0) { + $errorMsg = __('The payment is REFUSED.'); + // this will result the specific error + throw new ValidatorException($errorMsg); + } elseif (!in_array($resultCode, self::VALID_RESULT_CODES, true)) { + $errorMsg = __('Error with payment method please select different payment method.'); + throw new ValidatorException($errorMsg); } } /** * @throws ValidatorException */ - private function handleEmptyResultCode($response): never + private function handleEmptyResultCode(array $response): void { if (!empty($response['error'])) { $this->adyenLogger->error($response['error']); } - if (!empty($response['errorCode']) && !empty($response['error']) && in_array($response['errorCode'], self::ALLOWED_ERROR_CODES, true)) { + if (!empty($response['errorCode']) && + !empty($response['error']) && + in_array($response['errorCode'], AbstractAdyenException::SAFE_ERROR_CODES, true)) { $errorMsg = __($response['error']); } else { $errorMsg = __('Error with payment method, please select a different payment method.'); diff --git a/Gateway/Validator/DonateResponseValidator.php b/Gateway/Validator/DonateResponseValidator.php index be613bb208..5abd3657ea 100644 --- a/Gateway/Validator/DonateResponseValidator.php +++ b/Gateway/Validator/DonateResponseValidator.php @@ -3,7 +3,7 @@ * * Adyen Payment Module * - * Copyright (c) 2021 Adyen N.V. + * Copyright (c) 2025 Adyen N.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * @@ -13,7 +13,6 @@ namespace Adyen\Payment\Gateway\Validator; use Adyen\Payment\Logger\AdyenLogger; -use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Validator\AbstractValidator; use Magento\Payment\Gateway\Validator\ResultInterface; @@ -21,33 +20,42 @@ class DonateResponseValidator extends AbstractValidator { + const VALID_RESULT_CODES = ['Authorised', 'Received']; + /** - * @var AdyenLogger + * @param ResultInterfaceFactory $resultInterfaceFactory + * @param AdyenLogger $adyenLogger */ - private $adyenLogger; - - public function __construct(ResultInterfaceFactory $resultInterfaceFactory, AdyenLogger $adyenLogger) - { + public function __construct( + ResultInterfaceFactory $resultInterfaceFactory, + private readonly AdyenLogger $adyenLogger + ) { parent::__construct($resultInterfaceFactory); - $this->adyenLogger = $adyenLogger; } /** - * @inheritDoc - * @throws LocalizedException + * @param array $validationSubject + * @return ResultInterface */ public function validate(array $validationSubject): ResultInterface { $response = SubjectReader::readResponse($validationSubject); - if (empty($response['payment']['resultCode'])) { + $isValid = true; + $errorMessages = []; + + if (empty($response['payment']['resultCode']) || + !in_array($response['payment']['resultCode'], self::VALID_RESULT_CODES)) { if (!empty($response['error'])) { - $this->adyenLogger->error($response['error']); + $this->adyenLogger->error( + sprintf("An error occurred with the donation: %s", $response['error']) + ); } - throw new LocalizedException(__('An error occurred with the donation.')); + $isValid = false; + $errorMessages[] = __('An error occurred with the donation.'); } - return $this->createResult(in_array($response['payment']['resultCode'], ['Authorised', 'Received'])); + return $this->createResult($isValid, $errorMessages); } } diff --git a/Gateway/Validator/InstallmentValidator.php b/Gateway/Validator/InstallmentRequestValidator.php similarity index 65% rename from Gateway/Validator/InstallmentValidator.php rename to Gateway/Validator/InstallmentRequestValidator.php index 43d2a8c0bc..6b8463c9cb 100644 --- a/Gateway/Validator/InstallmentValidator.php +++ b/Gateway/Validator/InstallmentRequestValidator.php @@ -3,112 +3,79 @@ * * Adyen Payment Module * - * Copyright (c) 2017 Adyen B.V. + * Copyright (c) 2025 Adyen N.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * * Author: Adyen */ - namespace Adyen\Payment\Gateway\Validator; use Adyen\Payment\Helper\Config; -use Adyen\Payment\Helper\CaseManagement; use Adyen\Payment\Helper\ChargedCurrency; -use Adyen\Payment\Helper\Data; -use Adyen\Payment\Logger\AdyenLogger; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Serialize\SerializerInterface; use Magento\Payment\Gateway\Validator\AbstractValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; use Magento\Quote\Model\QuoteRepository; -class InstallmentValidator extends AbstractValidator +class InstallmentRequestValidator extends AbstractValidator { /** - * @var \Adyen\Payment\Logger\AdyenLogger - */ - private $adyenLogger; - - /** - * @var \Adyen\Payment\Helper\Config - */ - private $configHelper; - - /** - * @var \Adyen\Payment\Helper\Data - */ - private $adyenHelper; - - /** - * @var \Magento\Framework\Serialize\SerializerInterface - */ - private $serializer; - - /** - * @var \Magento\Quote\Model\QuoteRepository - */ - private $quoteRepository; - - /** - * @var ChargedCurrency - */ - private $chargedCurrency; - - - /** - * InstallmentValidator constructor. - * * @param ResultInterfaceFactory $resultFactory * @param Config $configHelper - * @param AdyenLogger $adyenLogger - * @param Data $adyenHelper * @param SerializerInterface $serializer * @param QuoteRepository $quoteRepository * @param ChargedCurrency $chargedCurrency */ public function __construct( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory, - \Adyen\Payment\Helper\Config $configHelper, - \Adyen\Payment\Logger\AdyenLogger $adyenLogger, - \Adyen\Payment\Helper\Data $adyenHelper, - \Magento\Framework\Serialize\SerializerInterface $serializer, - \Magento\Quote\Model\QuoteRepository $quoteRepository, - ChargedCurrency $chargedCurrency + ResultInterfaceFactory $resultFactory, + private readonly Config $configHelper, + private readonly SerializerInterface $serializer, + private readonly QuoteRepository $quoteRepository, + private readonly ChargedCurrency $chargedCurrency ) { - $this->adyenLogger = $adyenLogger; - $this->adyenHelper = $adyenHelper; - $this->configHelper = $configHelper; - $this->serializer = $serializer; - $this->quoteRepository = $quoteRepository; - $this->chargedCurrency = $chargedCurrency; parent::__construct($resultFactory); } - public function validate(array $validationSubject) + /** + * @param array $validationSubject + * @return ResultInterface + * @throws NoSuchEntityException + */ + public function validate(array $validationSubject): ResultInterface { $isValid = true; $fails = []; $payment = $validationSubject['payment']; $quoteId = $payment->getQuoteId(); + //This validator also runs for other payments that don't necesarily have a quoteId if ($quoteId) { $quote = $this->quoteRepository->get($quoteId); } else { $quote = false; } + $installmentsEnabled = $this->configHelper->getAdyenCcConfigData('enable_installments'); if ($quote && $installmentsEnabled) { $grandTotal = $this->chargedCurrency->getQuoteAmountCurrency($quote)->getAmount(); + $installmentsAvailable = $this->configHelper->getAdyenCcConfigData('installments'); $installmentSelected = $payment->getAdditionalInformation('number_of_installments'); + $ccType = $payment->getAdditionalInformation('cc_type'); + if ($installmentsAvailable) { $installments = $this->serializer->unserialize($installmentsAvailable); } + if ($installmentSelected && $installmentsAvailable) { $isValid = false; $fails[] = __('Installments not valid.'); + if ($installments) { foreach ($installments as $ccTypeInstallment => $installment) { if ($ccTypeInstallment == $ccType) { @@ -126,6 +93,7 @@ public function validate(array $validationSubject) } } } + return $this->createResult($isValid, $fails); } } diff --git a/Gateway/Validator/PayByLinkValidator.php b/Gateway/Validator/PayByLinkValidator.php deleted file mode 100644 index f37b52870b..0000000000 --- a/Gateway/Validator/PayByLinkValidator.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Validator; - -use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; -use Magento\Payment\Gateway\Validator\AbstractValidator; - -class PayByLinkValidator extends AbstractValidator -{ - /** - * @inheritdoc - */ - public function validate(array $validationSubject) - { - $payment = $validationSubject['payment']; - $expiresAt = $payment->getAdyenPblExpiresAt() . ' 23:59:59'; - if (!is_null($expiresAt) && $expiryDate = date_create_from_format( - AdyenPayByLinkConfigProvider::DATE_TIME_FORMAT, - $expiresAt - )) { - $daysToExpire = ($expiryDate->getTimestamp() - time()) / 86400; - if ( - $daysToExpire <= AdyenPayByLinkConfigProvider::MIN_EXPIRY_DAYS || - $daysToExpire >= AdyenPayByLinkConfigProvider::MAX_EXPIRY_DAYS - ) { - return $this->createResult(false, ['Invalid expiry date selected for Adyen Pay By Link']); - } - } - return $this->createResult(true); - } -} diff --git a/Gateway/Validator/PaymentLinksRequestValidator.php b/Gateway/Validator/PaymentLinksRequestValidator.php new file mode 100644 index 0000000000..d03f0b9404 --- /dev/null +++ b/Gateway/Validator/PaymentLinksRequestValidator.php @@ -0,0 +1,45 @@ + + */ + +namespace Adyen\Payment\Gateway\Validator; + +use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; +use Magento\Payment\Gateway\Validator\AbstractValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; + +class PaymentLinksRequestValidator extends AbstractValidator +{ + /** + * @param array $validationSubject + * @return ResultInterface + */ + public function validate(array $validationSubject): ResultInterface + { + $payment = $validationSubject['payment']; + $expiresAt = $payment->getAdyenPblExpiresAt() . ' 23:59:59'; + + $isValid = true; + $errorMessages = []; + + if ($expiryDate = date_create_from_format(AdyenPayByLinkConfigProvider::DATE_TIME_FORMAT, $expiresAt)) { + $daysToExpire = ($expiryDate->getTimestamp() - time()) / 86400; + + if ($daysToExpire <= AdyenPayByLinkConfigProvider::MIN_EXPIRY_DAYS || + $daysToExpire >= AdyenPayByLinkConfigProvider::MAX_EXPIRY_DAYS + ) { + $isValid = false; + $errorMessages[] = 'Invalid expiry date selected for Adyen Pay By Link'; + } + } + + return $this->createResult($isValid, $errorMessages); + } +} diff --git a/Gateway/Validator/PaymentLinksResponseValidator.php b/Gateway/Validator/PaymentLinksResponseValidator.php index 889e4cddf7..293de2a245 100644 --- a/Gateway/Validator/PaymentLinksResponseValidator.php +++ b/Gateway/Validator/PaymentLinksResponseValidator.php @@ -1,21 +1,9 @@ @@ -23,9 +11,7 @@ namespace Adyen\Payment\Gateway\Validator; -use Adyen\Payment\Helper\Data; use Adyen\Payment\Logger\AdyenLogger; -use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Validator\AbstractValidator; use Magento\Payment\Gateway\Validator\ResultInterface; @@ -34,29 +20,13 @@ class PaymentLinksResponseValidator extends AbstractValidator { /** - * @var AdyenLogger - */ - private $adyenLogger; - - /** - * @var Data - */ - private $adyenHelper; - - /** - * CheckoutResponseValidator constructor. - * * @param ResultInterfaceFactory $resultFactory * @param AdyenLogger $adyenLogger - * @param Data $adyenHelper */ public function __construct( ResultInterfaceFactory $resultFactory, - AdyenLogger $adyenLogger, - Data $adyenHelper + private readonly AdyenLogger $adyenLogger ) { - $this->adyenLogger = $adyenLogger; - $this->adyenHelper = $adyenHelper; parent::__construct($resultFactory); } @@ -64,7 +34,7 @@ public function __construct( * @param array $validationSubject * @return ResultInterface */ - public function validate(array $validationSubject) + public function validate(array $validationSubject): ResultInterface { $response = SubjectReader::readResponse($validationSubject); @@ -78,8 +48,7 @@ public function validate(array $validationSubject) $this->adyenLogger->error($response['error']); } - $errorMsg = __('Error with payment method, please select a different payment method.'); - throw new LocalizedException($errorMsg); + $errorMessages[] = __('Error with payment method, please select a different payment method.'); } return $this->createResult($isValid, $errorMessages); diff --git a/Gateway/Validator/RefundResponseValidator.php b/Gateway/Validator/RefundResponseValidator.php deleted file mode 100644 index 47c8745c54..0000000000 --- a/Gateway/Validator/RefundResponseValidator.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Validator; - -use Magento\Payment\Gateway\Validator\AbstractValidator; - -class RefundResponseValidator extends AbstractValidator -{ - /** - * @var \Adyen\Payment\Logger\AdyenLogger - */ - private $adyenLogger; - - /** - * RefundResponseValidator constructor. - * - * @param \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory - * @param \Adyen\Payment\Logger\AdyenLogger $adyenLogger - */ - public function __construct( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory $resultFactory, - \Adyen\Payment\Logger\AdyenLogger $adyenLogger - ) { - $this->adyenLogger = $adyenLogger; - parent::__construct($resultFactory); - } - - /** - * @param array $validationSubject - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function validate(array $validationSubject) - { - $responses = \Magento\Payment\Gateway\Helper\SubjectReader::readResponse($validationSubject); - - $isValid = true; - $errorMessages = []; - - foreach ($responses as $response) { - if (empty($response['status']) || $response['status'] != 'received') { - $errorMsg = __('Error with refund'); - $this->adyenLogger->error($errorMsg); - - if (!empty($response['error'])) { - $this->adyenLogger->error($response['error']); - } - - $errorMessages[] = $errorMsg; - $isValid = false; - } - } - - return $this->createResult($isValid, $errorMessages); - } -} diff --git a/Helper/Data.php b/Helper/Data.php index b5116c9560..34cdb20489 100755 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -561,8 +561,9 @@ public function getLiveEndpointPrefix($storeId = null) * Cancels the order * * @param $order + * @param null $reason */ - public function cancelOrder($order) + public function cancelOrder($order, $reason = null) { $orderStatus = $this->configHelper->getAdyenAbstractConfigData('payment_cancelled'); $order->setActionFlag($orderStatus, true); @@ -577,12 +578,11 @@ public function cancelOrder($order) if ($order->canCancel()) { if ($this->orderManagement->cancel($order->getEntityId())) { //new canceling process try { - $orderStatusHistory = $this->orderStatusHistoryFactory->create() - ->setParentId($order->getEntityId()) - ->setEntityName('order') - ->setStatus(Order::STATE_CANCELED) - ->setComment(__('Order has been cancelled by "%1" payment response.', $order->getPayment()->getMethod())); - $this->orderManagement->addComment($order->getEntityId(), $orderStatusHistory); + $comment = __('Order has been cancelled.', $order->getPayment()->getMethod()); + if ($reason) { + $comment .= '
' . __("Reason: %1", $reason) . '
'; + } + $order->addCommentToStatusHistory($comment, $order->getStatus()); } catch (Exception $e) { $this->adyenLogger->addAdyenDebug( __('Order cancel history comment error: %1', $e->getMessage()), diff --git a/Helper/Invoice.php b/Helper/Invoice.php index 75183d1fda..30e6df3f4c 100644 --- a/Helper/Invoice.php +++ b/Helper/Invoice.php @@ -219,11 +219,11 @@ public function createAdyenInvoice( * * @param Order $order * @param Notification $notification - * @return AdyenInvoice + * @return array + * @throws AdyenWebhookException * @throws AlreadyExistsException - * @throws Exception */ - public function handleCaptureWebhook(Order $order, Notification $notification): AdyenInvoice + public function handleCaptureWebhook(Order $order, Notification $notification): array { $invoiceFactory = $this->adyenInvoiceFactory->create(); $adyenInvoice = $this->adyenInvoiceResourceModel->getAdyenInvoiceByCaptureWebhook($order, $notification); @@ -280,12 +280,19 @@ public function handleCaptureWebhook(Order $order, Notification $notification): $magentoInvoice = $this->magentoInvoiceFactory->create()->load($adyenInvoiceObject->getInvoiceId()); if ($this->isFullInvoiceAmountManuallyCaptured($magentoInvoice)) { + /* + * Magento Invoice updates the Order object while paying the invoice. This creates two divergent + * Order objects. In the downstream, some information might be missing due to setting them on the + * wrong order object as the Order might be already updated in the upstream without persisting + * it to the database. Setting the order again on the Invoice makes sure we are dealing + * with the same order object always. + */ + $magentoInvoice->setOrder($order); $magentoInvoice->pay(); $this->invoiceRepository->save($magentoInvoice); - $this->magentoOrderResourceModel->save($magentoInvoice->getOrder()); } - return $adyenInvoiceObject; + return [$adyenInvoiceObject, $magentoInvoice, $order]; } /** diff --git a/Helper/OrderStatusHistory.php b/Helper/OrderStatusHistory.php new file mode 100644 index 0000000000..a56633ad93 --- /dev/null +++ b/Helper/OrderStatusHistory.php @@ -0,0 +1,140 @@ + + */ + +namespace Adyen\Payment\Helper; + +use Adyen\Payment\Api\Data\NotificationInterface; +use Adyen\Payment\Model\Notification; +use Magento\Sales\Api\Data\OrderInterface; + +class OrderStatusHistory +{ + /** + * @param ChargedCurrency $chargedCurrencyHelper + * @param Data $adyenHelper + */ + public function __construct( + private readonly ChargedCurrency $chargedCurrencyHelper, + private readonly Data $adyenHelper + ) { } + + /** + * Builds the order status history comment based on the Checkout API response + * + * @param array $response + * @param string $actionName + * @return string + */ + public function buildApiResponseComment(array $response, string $actionName): string + { + $comment = '' . __("Adyen %1 response", $actionName) . '
'; + + if (isset($response['resultCode'])) { + $comment .= __("Result code: %1", $response['resultCode']) . '
'; + } + + // Modification responses contain `status` but not `resultCode`. + if (isset($response['status'])) { + $comment .= __("Status: %1", $response['status']) . '
'; + } + + if (isset($response['pspReference'])) { + $comment .= __("PSP reference: %1", $response['pspReference']) . '
'; + } + + if ($paymentMethod = $response['paymentMethod']['brand'] ?? $response['paymentMethod']['type'] ?? null) { + $comment .= __("Payment method: %1", $paymentMethod) . '
'; + } + + if (isset($response['refusalReason'])) { + $comment .= __("Refusal reason: %1", $response['refusalReason']) . '
'; + } + + if (isset($response['errorCode'])) { + $comment .= __("Error code: %1", $response['errorCode']) . '
'; + } + + return $comment; + } + + /** + * Builds the order status history comment based on the webhook + * + * @param OrderInterface $order + * @param NotificationInterface $notification + * @param string|null $klarnaReservationNumber + * @return string + */ + public function buildWebhookComment( + OrderInterface $order, + NotificationInterface $notification, + ?string $klarnaReservationNumber = null, + ): string { + $comment = '' . __( + "Adyen %1%2 webhook", + $this->getIsWebhookForPartialPayment($order, $notification) ? 'partial ': '', + $notification->getEventCode() + ) . '
'; + + if (!empty($notification->getPspreference())) { + $comment .= __("PSP reference: %1", $notification->getPspreference()) . '
'; + } + + if (!empty($notification->getPaymentMethod())) { + $comment .= __("Payment method: %1", $notification->getPaymentMethod()) . '
'; + } + + if (!empty($notification->getSuccess())) { + $status = $notification->isSuccessful() ? 'Successful' : 'Failed'; + $comment .= __("Event status: %1", $status) . '
'; + } + + if (!empty($notification->getReason())) { + $comment .= __("Reason: %1", $notification->getReason()) . '
'; + } + + if (isset($klarnaReservationNumber)) { + $comment .= __("Reservation number: %1", $klarnaReservationNumber) . '
'; + } + + return $comment; + } + + /** + * Identifies whether if the notification belongs to a partial modification or not + * + * @param OrderInterface $order + * @param NotificationInterface $notification + * @return bool + */ + private function getIsWebhookForPartialPayment( + OrderInterface $order, + NotificationInterface $notification + ): bool { + $isPartial = false; + + if (in_array($notification->getEventCode(), [Notification::REFUND, Notification::CAPTURE])) { + // check if it is a full or partial refund + $amount = $notification->getAmountValue(); + $orderAmountCurrency = $this->chargedCurrencyHelper->getOrderAmountCurrency($order, false); + $formattedOrderAmount = $this->adyenHelper->formatAmount( + $orderAmountCurrency->getAmount(), + $orderAmountCurrency->getCurrencyCode() + ); + + if ($amount !== $formattedOrderAmount) { + $isPartial = true; + } + } + + return $isPartial; + } +} diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php index ee1f0f2831..8807210925 100644 --- a/Helper/PaymentMethods.php +++ b/Helper/PaymentMethods.php @@ -54,6 +54,7 @@ class PaymentMethods extends AbstractHelper const ADYEN_ONE_CLICK = 'adyen_oneclick'; const ADYEN_PAY_BY_LINK = 'adyen_pay_by_link'; const ADYEN_PAYPAL = 'adyen_paypal'; + const ADYEN_BOLETO = 'adyen_boleto'; const ADYEN_PREFIX = 'adyen_'; const ADYEN_CC_VAULT = 'adyen_cc_vault'; const METHODS_WITH_BRAND_LOGO = [ diff --git a/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php index d74b3e1d63..218d31c1cf 100644 --- a/Helper/PaymentResponseHandler.php +++ b/Helper/PaymentResponseHandler.php @@ -12,6 +12,7 @@ namespace Adyen\Payment\Helper; use Adyen\Model\Checkout\CancelOrderRequest; +use Adyen\Payment\Helper\Order as AdyenOrderHelper; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\ResourceModel\PaymentResponse\CollectionFactory as PaymentResponseCollectionFactory; use Exception; @@ -50,48 +51,35 @@ class PaymentResponseHandler ]; /** - * @var AdyenLogger + * @param AdyenLogger $adyenLogger + * @param Vault $vaultHelper + * @param Order $orderResourceModel + * @param Data $dataHelper + * @param Quote $quoteHelper + * @param AdyenOrderHelper $orderHelper + * @param OrderRepository $orderRepository + * @param HistoryFactory $orderHistoryFactory + * @param StateData $stateDataHelper + * @param PaymentResponseCollectionFactory $paymentResponseCollectionFactory + * @param Config $configHelper + * @param PaymentMethods $paymentMethodsHelper + * @param OrderStatusHistory $orderStatusHistoryHelper */ - private AdyenLogger $adyenLogger; - private Vault $vaultHelper; - private Order $orderResourceModel; - private Data $dataHelper; - private Quote $quoteHelper; - private \Adyen\Payment\Helper\Order $orderHelper; - private OrderRepository $orderRepository; - private HistoryFactory $orderHistoryFactory; - private StateData $stateDataHelper; - private PaymentResponseCollectionFactory $paymentResponseCollectionFactory; - private Config $configHelper; - private PaymentMethods $paymentMethodsHelper; - public function __construct( - AdyenLogger $adyenLogger, - Vault $vaultHelper, - Order $orderResourceModel, - Data $dataHelper, - Quote $quoteHelper, - \Adyen\Payment\Helper\Order $orderHelper, - OrderRepository $orderRepository, - HistoryFactory $orderHistoryFactory, - StateData $stateDataHelper, - PaymentResponseCollectionFactory $paymentResponseCollectionFactory, - Config $configHelper, - PaymentMethods $paymentMethodsHelper - ) { - $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; - $this->paymentResponseCollectionFactory = $paymentResponseCollectionFactory; - $this->configHelper = $configHelper; - $this->paymentMethodsHelper = $paymentMethodsHelper; - } + private readonly AdyenLogger $adyenLogger, + private readonly Vault $vaultHelper, + private readonly Order $orderResourceModel, + private readonly Data $dataHelper, + private readonly Quote $quoteHelper, + private readonly AdyenOrderHelper $orderHelper, + private readonly OrderRepository $orderRepository, + private readonly HistoryFactory $orderHistoryFactory, + private readonly StateData $stateDataHelper, + private readonly PaymentResponseCollectionFactory $paymentResponseCollectionFactory, + private readonly Config $configHelper, + private readonly PaymentMethods $paymentMethodsHelper, + private readonly OrderStatusHistory $orderStatusHistoryHelper + ) { } public function formatPaymentResponse( string $resultCode, @@ -170,23 +158,6 @@ public function handlePaymentsDetailsResponse( return false; } - $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 - ); - $resultCode = $paymentsDetailsResponse['resultCode']; if (!empty($resultCode)) { $payment->setAdditionalInformation('resultCode', $resultCode); @@ -234,9 +205,15 @@ public function handlePaymentsDetailsResponse( * Otherwise keep the order state as pending_payment. */ $order = $this->orderHelper->setStatusOrderCreation($order); - $this->orderRepository->save($order); } + // Add order status history comment for /payments/details API response + $comment = $this->orderStatusHistoryHelper->buildApiResponseComment( + $paymentsDetailsResponse, + 'payments/details' + ); + $order->addCommentToStatusHistory($comment, $order->getStatus()); + // Cleanup state data if exists. try { $this->stateDataHelper->cleanQuoteStateData($order->getQuoteId(), $authResult); @@ -244,6 +221,10 @@ public function handlePaymentsDetailsResponse( $this->adyenLogger->error(__('Error cleaning the payment state data: %s', $exception->getMessage())); } + $paymentMethod = $paymentsDetailsResponse['paymentMethod']['brand'] ?? + $paymentsDetailsResponse['paymentMethod']['type'] ?? + ''; + switch ($resultCode) { case self::AUTHORISED: if (!empty($paymentsDetailsResponse['pspReference'])) { @@ -273,16 +254,18 @@ public function handlePaymentsDetailsResponse( // do nothing wait for the notification if (strpos((string) $paymentMethod, "bankTransfer") !== false) { - $comment .= "

Waiting for the customer to transfer the money."; + $pendingComment = "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."; + $pendingComment = "This request will be send to the bank at the end of the day."; } else { - $comment .= "

The payment result is not confirmed (yet). + $pendingComment = "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."; } + + $order->addCommentToStatusHistory($pendingComment, $order->getStatus()); $this->adyenLogger->addAdyenResult('Do nothing wait for the notification'); $result = true; @@ -318,7 +301,7 @@ public function handlePaymentsDetailsResponse( if ($order->canCancel()) { // Proceed to set cancellation action flag and cancel the order $order->setActionFlag(\Magento\Sales\Model\Order::ACTION_FLAG_CANCEL, true); - $this->dataHelper->cancelOrder($order); + $this->dataHelper->cancelOrder($order, $resultCode); } else { $this->adyenLogger->addAdyenResult('The order cannot be cancelled'); } @@ -337,14 +320,6 @@ public function handlePaymentsDetailsResponse( break; } - $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); diff --git a/Helper/Webhook.php b/Helper/Webhook.php index ddaac33fbf..57b2b10dbd 100644 --- a/Helper/Webhook.php +++ b/Helper/Webhook.php @@ -51,34 +51,28 @@ class Webhook 'payment_authorized' => [Order::STATE_PROCESSING] ]; - // TODO::This property is not written but only is read. Check the usage. - private $boletoPaidAmount; private ?string $klarnaReservationNumber; private ?string $ratepayDescriptor; /** - * @param Data $adyenHelper * @param SerializerInterface $serializer * @param TimezoneInterface $timezone * @param Config $configHelper - * @param ChargedCurrency $chargedCurrency * @param AdyenLogger $logger * @param WebhookHandlerFactory $webhookHandlerFactory * @param OrderHelper $orderHelper * @param OrderRepository $orderRepository - * @param PaymentMethods $paymentMethodsHelper + * @param OrderStatusHistory $orderStatusHistoryHelper */ public function __construct( - private readonly Data $adyenHelper, private readonly SerializerInterface $serializer, private readonly TimezoneInterface $timezone, private readonly ConfigHelper $configHelper, - private readonly ChargedCurrency $chargedCurrency, private readonly AdyenLogger $logger, private readonly WebhookHandlerFactory $webhookHandlerFactory, private readonly OrderHelper $orderHelper, private readonly OrderRepository $orderRepository, - private readonly PaymentMethods $paymentMethodsHelper + private readonly OrderStatusHistory $orderStatusHistoryHelper ) { $this->klarnaReservationNumber = null; $this->ratepayDescriptor = null; @@ -287,76 +281,17 @@ private function declareVariables(Notification $notification): void private function addNotificationDetailsHistoryComment(Order $order, Notification $notification): Order { - $successResult = $notification->isSuccessful() ? 'true' : 'false'; - $reason = $notification->getReason(); - $success = (!empty($reason)) ? "$successResult
reason:$reason" : $successResult; - - $payment = $order->getPayment(); - $paymentMethodInstance = $payment->getMethodInstance(); - - $eventCode = $notification->getEventCode(); - if ($eventCode == Notification::REFUND || $eventCode == Notification::CAPTURE) { - // check if it is a full or partial refund - $amount = $notification->getAmountValue(); - $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order, false); - $formattedOrderAmount = $this->adyenHelper - ->formatAmount($orderAmountCurrency->getAmount(), $orderAmountCurrency->getCurrencyCode()); - - if ($amount == $formattedOrderAmount) { - $order->setData( - 'adyen_notification_event_code', - $eventCode . " : " . strtoupper($successResult) - ); - } else { - $order->setData( - 'adyen_notification_event_code', - "(PARTIAL) " . - $eventCode . " : " . strtoupper($successResult) - ); - } - } else { - $order->setData( - 'adyen_notification_event_code', - $eventCode . " : " . strtoupper($successResult) - ); - } - - // if payment method is klarna, ratepay or openinvoice/afterpay show the reservartion number - if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance) && - !empty($this->klarnaReservationNumber)) { - $klarnaReservationNumberText = "
reservationNumber: " . $this->klarnaReservationNumber; - } else { - $klarnaReservationNumberText = ""; - } - - if ($this->boletoPaidAmount != null && $this->boletoPaidAmount != "") { - $boletoPaidAmountText = "
Paid amount: " . $this->boletoPaidAmount; - } else { - $boletoPaidAmountText = ""; - } - - $type = 'Adyen HTTP Notification(s):'; - $comment = __( - '%1
eventCode: %2
pspReference: %3
paymentMethod: %4
' . - ' success: %5 %6 %7', - $type, - $eventCode, - $notification->getPspreference(), - $notification->getPaymentMethod(), - $success, - $klarnaReservationNumberText, - $boletoPaidAmountText - ); + $comment = $this->orderStatusHistoryHelper->buildWebhookComment($order, $notification); // If notification is pending status and pending status is set add the status change to the comment history - if ($eventCode == Notification::PENDING) { + if ($notification->getEventCode() == Notification::PENDING) { $pendingStatus = $this->configHelper->getConfigData( 'pending_status', 'adyen_abstract', $order->getStoreId() ); if ($pendingStatus != "") { - $order->addStatusHistoryComment($comment, $pendingStatus); + $order->addCommentToStatusHistory($comment, $pendingStatus); $this->logger->addAdyenNotification( 'Created comment history for this notification with status change to: ' . $pendingStatus, array_merge( @@ -368,7 +303,7 @@ private function addNotificationDetailsHistoryComment(Order $order, Notification } } - $order->addStatusHistoryComment($comment, $order->getStatus()); + $order->addCommentToStatusHistory($comment, $order->getStatus()); $this->logger->addAdyenNotification( 'Created comment history for this notification', [ @@ -465,10 +400,6 @@ private function updateOrderPaymentWithAdyenAttributes( ); } - if ($this->boletoPaidAmount != "") { - $payment->setAdditionalInformation('adyen_boleto_paid_amount', $this->boletoPaidAmount); - } - if ($this->ratepayDescriptor !== "") { $payment->setAdditionalInformation( 'adyen_ratepay_descriptor', @@ -521,7 +452,7 @@ private function setNotificationError(Notification $notification, string $errorM private function addNotificationErrorComment(Order $order, string $errorMessage): Order { $comment = __('The order failed to update: %1', $errorMessage); - $order->addStatusHistoryComment($comment, $order->getStatus()); + $order->addCommentToStatusHistory($comment, $order->getStatus()); $this->orderRepository->save($order); return $order; } diff --git a/Helper/Webhook/CaptureWebhookHandler.php b/Helper/Webhook/CaptureWebhookHandler.php index 77914f851d..9504a60b23 100644 --- a/Helper/Webhook/CaptureWebhookHandler.php +++ b/Helper/Webhook/CaptureWebhookHandler.php @@ -93,10 +93,9 @@ public function handleWebhook(MagentoOrder $order, Notification $notification, s return $order; } - // TODO Get functionality out of the invoiceHelper function, so we don't have to fetch the order from the db - $adyenInvoice = $this->invoiceHelper->handleCaptureWebhook($order, $notification); - // Refresh the order by fetching it from the db - $order = $this->orderHelper->fetchOrderByIncrementId($notification); + list($adyenInvoice, $magentoInvoice, $order) = + $this->invoiceHelper->handleCaptureWebhook($order, $notification); + $adyenOrderPayment = $this->adyenOrderPaymentFactory->create()->load($adyenInvoice->getAdyenPaymentOrderId(), OrderPaymentInterface::ENTITY_ID); $this->adyenOrderPaymentHelper->refreshPaymentCaptureStatus($adyenOrderPayment, $notification->getAmountCurrency()); $this->adyenLogger->addAdyenNotification( @@ -112,7 +111,6 @@ public function handleWebhook(MagentoOrder $order, Notification $notification, s ] ); - $magentoInvoice = $this->magentoInvoiceFactory->create()->load($adyenInvoice->getInvoiceId(), MagentoInvoice::ENTITY_ID); $this->adyenLogger->addAdyenNotification( sprintf('Notification %s updated invoice {invoiceId}', $notification->getEntityId()), array_merge( diff --git a/Test/Unit/Gateway/Response/CheckoutPaymentCommentHistoryHandlerTest.php b/Test/Unit/Gateway/Response/CheckoutPaymentCommentHistoryHandlerTest.php deleted file mode 100644 index fc6f90b549..0000000000 --- a/Test/Unit/Gateway/Response/CheckoutPaymentCommentHistoryHandlerTest.php +++ /dev/null @@ -1,122 +0,0 @@ -checkoutPaymentCommentHistoryHandler = new CheckoutPaymentCommentHistoryHandler(); - - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); - $this->orderMock = $this->createMock(Order::class); - - $this->paymentMock = $this->createMock(Payment::class); - $this->paymentMock->method('getOrder')->willReturn($this->orderMock); - $this->paymentDataObject = new PaymentDataObject($orderAdapterMock, $this->paymentMock); - - $this->handlingSubject = [ - 'payment' => $this->paymentDataObject, - 'paymentAction' => "authorize", - 'stateObject' => null - ]; - } - public function testIfGeneralFlowIsHandledCorrectly() - { - // Prepare the sample response collection - $responseCollection = [ - 'hasOnlyGiftCards' => false, - 0 => [ - 'additionalData' => [], - 'amount' => [], - 'resultCode' => 'Authorised', - 'pspReference' => 'MDH54321', - 'paymentMethod' => [ - 'name' => 'giftcard', - 'type' => 'Givex', - ], - ], - 1 => [ - 'additionalData' => [], - 'amount' => [], - 'resultCode' => 'Authorised', - 'pspReference' => 'ABC12345', - 'paymentMethod' => [ - 'name' => 'card', - 'type' => 'CreditCard', - ], - ], - ]; - - // Set expectations for the mocked order object - $this->orderMock - ->expects($this->once()) - ->method('addStatusHistoryComment') - ->with( - $this->stringContains( - "authResult: Authorised
pspReference: MDH54321
" . - "
authResult: Authorised
pspReference: ABC12345
" - ), - $this->anything() - ); - - $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection); - } - - public function testIfEmptyResponseCodeIsHandledCorrectly() - { - // Prepare a sample response collection without a resultCode - $responseCollection = [ - [ - 'pspReference' => '123456789' - ] - ]; - - // Set expectations for the mocked order object - $this->orderMock - ->expects($this->once()) - ->method('addStatusHistoryComment') - ->with( - $this->stringContains('pspReference: 123456789'), - $this->anything() - ); - - // Execute the handler - $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection); - } - - public function testIfNoPspReferenceIsHandledCorrectly() - { - // Prepare a sample response collection without a pspReference - $responseCollection = [ - [ - 'resultCode' => 'Authorised' - ] - ]; - - // Set expectations for the mocked order object - $this->orderMock - ->expects($this->once()) - ->method('addStatusHistoryComment') - ->with( - $this->stringContains('authResult: Authorised'), - $this->anything() - ); - - // Execute the handler - $this->checkoutPaymentCommentHistoryHandler->handle($this->handlingSubject, $responseCollection); - } -} diff --git a/Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php b/Test/Unit/Gateway/Response/CheckoutPaymentsResponseHandlerTest.php similarity index 92% rename from Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php rename to Test/Unit/Gateway/Response/CheckoutPaymentsResponseHandlerTest.php index aa48177709..3b610cbae5 100644 --- a/Test/Unit/Gateway/Response/CheckoutPaymentsDetailsHandlerTest.php +++ b/Test/Unit/Gateway/Response/CheckoutPaymentsResponseHandlerTest.php @@ -2,8 +2,9 @@ namespace Test\Unit\Gateway\Response; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; -use Adyen\Payment\Gateway\Response\CheckoutPaymentsDetailsHandler; +use Adyen\Payment\Gateway\Response\CheckoutPaymentsResponseHandler; use Adyen\Payment\Helper\Data; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\PaymentDataObject; @@ -11,9 +12,9 @@ use Magento\Sales\Model\Order\Payment; use PHPUnit\Framework\MockObject\MockObject; -class CheckoutPaymentsDetailsHandlerTest extends AbstractAdyenTestCase +class CheckoutPaymentsResponseHandlerTest extends AbstractAdyenTestCase { - private CheckoutPaymentsDetailsHandler $checkoutPaymentsDetailsHandler; + private CheckoutPaymentsResponseHandler $checkoutPaymentsDetailsHandler; private Payment|MockObject $paymentMock; private Order|MockObject $orderMock; private array $handlingSubject; @@ -21,7 +22,7 @@ class CheckoutPaymentsDetailsHandlerTest extends AbstractAdyenTestCase protected function setUp(): void { $this->adyenHelperMock = $this->createMock(Data::class); - $this->checkoutPaymentsDetailsHandler = new CheckoutPaymentsDetailsHandler($this->adyenHelperMock); + $this->checkoutPaymentsDetailsHandler = new CheckoutPaymentsResponseHandler($this->adyenHelperMock); $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); $this->orderMock = $this->createMock(Order::class); @@ -41,7 +42,6 @@ public function testIfGeneralFlowIsHandledCorrectly() { // prepare Handler input. $responseCollection = [ - 'hasOnlyGiftCards' => false, 0 => [ 'additionalData' => [], 'amount' => [], @@ -68,7 +68,6 @@ public function testIfBoletoSendsAnEmail() { // prepare Handler input. $responseCollection = [ - 'hasOnlyGiftCards' => false, 0 => [ 'additionalData' => [], 'amount' => [], @@ -80,7 +79,7 @@ public function testIfBoletoSendsAnEmail() $this->paymentMock ->expects($this->once()) ->method('getMethod') - ->willReturn(CheckoutPaymentsDetailsHandler::ADYEN_BOLETO); + ->willReturn(PaymentMethods::ADYEN_BOLETO); // for boleto it should not call this function. $this->orderMock @@ -97,7 +96,6 @@ public function testIfPartialPaymentHandlesLastPaymentResponse() { // prepare Handler input. $responseCollection = [ - 'hasOnlyGiftCards' => false, 0 => [ 'additionalData' => [], 'amount' => [], diff --git a/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php b/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php deleted file mode 100644 index 206fdd275c..0000000000 --- a/Test/Unit/Gateway/Response/VaultDetailsHandlerTest.php +++ /dev/null @@ -1,96 +0,0 @@ -vaultHelperMock = $this->createMock(Vault::class); - $this->vaultDetailsHandler = new VaultDetailsHandler($this->vaultHelperMock); - - $orderAdapterMock = $this->createMock(OrderAdapterInterface::class); - $orderMock = $this->createMock(Order::class); - - $this->paymentMock = $this->createMock(Payment::class); - $this->paymentMock->method('getOrder')->willReturn($orderMock); - $this->paymentDataObject = new PaymentDataObject($orderAdapterMock, $this->paymentMock); - - $this->handlingSubject = [ - 'payment' => $this->paymentDataObject, - 'paymentAction' => "authorize", - 'stateObject' => null - ]; - } - - public function testIfGeneralFlowIsHandledCorrectly() - { - // prepare Handler input. - $responseCollection = [ - 'hasOnlyGiftCards' => false, - 0 => [ - 'additionalData' => ['someData' => 'value'], - 'amount' => [], - 'resultCode' => 'Authorised', - ] - ]; - - // Ensure the vaultHelper's method is called once with the correct arguments. - $this->vaultHelperMock - ->expects($this->once()) - ->method('handlePaymentResponseRecurringDetails') - ->with($this->paymentMock, $responseCollection[0]); - - $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection); - } - - public function testIfPaymentsWithoutAdditionalDataAreIgnored() - { - // Prepare a responseCollection without additionalData - $responseCollection = [ - 'hasOnlyGiftCards' => false, - 0 => [ - 'additionalData' => [], - 'amount' => [], - 'resultCode' => 'Authorised', - ] - ]; - - // Ensure the vaultHelper's method is NOT called since additionalData is empty - $this->vaultHelperMock - ->expects($this->never()) - ->method('handlePaymentResponseRecurringDetails'); - - $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection); - } - - public function testIfGiftCardOnlyPaymentsAreIgnored() - { - $responseCollection = [ - 'hasOnlyGiftCards' => true, - 'additionalData' => ['someData' => 'value'], - 'amount' => [], - 'resultCode' => 'Authorised', - ]; - - // Ensure the vaultHelper's method is NOT called since additionalData is empty - $this->vaultHelperMock - ->expects($this->never()) - ->method('handlePaymentResponseRecurringDetails'); - - $this->vaultDetailsHandler->handle($this->handlingSubject, $responseCollection); - } -} diff --git a/Test/Unit/Gateway/Validator/PayByLinkValidatorTest.php b/Test/Unit/Gateway/Validator/PaymentLinksRequestValidatorTest.php similarity index 92% rename from Test/Unit/Gateway/Validator/PayByLinkValidatorTest.php rename to Test/Unit/Gateway/Validator/PaymentLinksRequestValidatorTest.php index e5587feae9..06c5cfb2af 100755 --- a/Test/Unit/Gateway/Validator/PayByLinkValidatorTest.php +++ b/Test/Unit/Gateway/Validator/PaymentLinksRequestValidatorTest.php @@ -11,16 +11,16 @@ namespace Adyen\Payment\Test\Gateway\Validator; -use Adyen\Payment\Gateway\Validator\PayByLinkValidator; +use Adyen\Payment\Gateway\Validator\PaymentLinksRequestValidator; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; use Magento\Quote\Model\Quote\Payment; use PHPUnit\Framework\TestCase; -class PayByLinkValidatorTest extends TestCase +class PaymentLinksRequestValidatorTest extends TestCase { /** - * @var PayByLinkValidator + * @var PaymentLinksRequestValidator */ private $payByLinkValidator; @@ -57,7 +57,7 @@ protected function setUp(): void ] ])); - $this->payByLinkValidator = new PayByLinkValidator($resultInterfaceFactory); + $this->payByLinkValidator = new PaymentLinksRequestValidator($resultInterfaceFactory); $this->payment = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor()->setMethods(['getAdyenPblExpiresAt']) diff --git a/etc/di.xml b/etc/di.xml index c078079d3d..a22690742e 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -846,7 +846,7 @@ AdyenPaymentCcVaultAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -886,8 +886,8 @@ AdyenPaymentVaultAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator - AdyenPaymentVaultResponseHandlerComposite + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator + AdyenPaymentResponseHandlerComposite @@ -917,8 +917,8 @@ AdyenPaymentPayByLinkInitializeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPaymentLinks - PaymentLinksResponseValidator - Adyen\Payment\Gateway\Response\PayByLinkResponseHandler + Adyen\Payment\Gateway\Validator\PaymentLinksResponseValidator + Adyen\Payment\Gateway\Response\CheckoutPaymentLinksResponseHandler @@ -927,7 +927,7 @@ AdyenPaymentCcAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -936,7 +936,7 @@ AdyenPaymentMotoAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -945,7 +945,7 @@ AdyenPaymentOneclickAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -954,7 +954,7 @@ AdyenPaymentAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -963,7 +963,7 @@ AdyenPaymentGiftcardAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionPayment - CheckoutResponseValidator + Adyen\Payment\Gateway\Validator\CheckoutResponseValidator AdyenPaymentResponseHandlerComposite @@ -982,7 +982,7 @@ AdyenPaymentCaptureRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionCapture - Adyen\Payment\Gateway\Validator\CaptureResponseValidator + AdyenPaymentCaptureResponseValidator AdyenPaymentCaptureResponseHandlerComposite @@ -991,7 +991,7 @@ AdyenPaymentMotoCaptureRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionCapture - Adyen\Payment\Gateway\Validator\CaptureResponseValidator + AdyenPaymentCaptureResponseValidator AdyenPaymentCaptureResponseHandlerComposite @@ -1001,7 +1001,7 @@ AdyenPaymentRefundRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionRefund - Adyen\Payment\Gateway\Validator\RefundResponseValidator + AdyenPaymentRefundResponseValidator AdyenPaymentRefundResponseHandlerComposite @@ -1010,7 +1010,7 @@ AdyenPaymentMotoRefundRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionRefund - Adyen\Payment\Gateway\Validator\RefundResponseValidator + AdyenPaymentRefundResponseValidator AdyenPaymentRefundResponseHandlerComposite @@ -1019,7 +1019,7 @@ AdyenPaymentCancelRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionCancel - Adyen\Payment\Gateway\Validator\CancelResponseValidator + AdyenPaymentCancelResponseValidator AdyenPaymentCancelResponseHandlerComposite @@ -1028,10 +1028,36 @@ AdyenPaymentMotoCancelRequest Adyen\Payment\Gateway\Http\TransferFactory Adyen\Payment\Gateway\Http\Client\TransactionCancel - Adyen\Payment\Gateway\Validator\CancelResponseValidator + AdyenPaymentCancelResponseValidator AdyenPaymentCancelResponseHandlerComposite + + + + capture + + Adyen\Model\Checkout\PaymentCaptureResponse::STATUS_RECEIVED + + + + + + refund + + Adyen\Model\Checkout\PaymentRefundResponse::STATUS_RECEIVED + + + + + + cancel + + Adyen\Model\Checkout\PaymentCancelResponse::STATUS_RECEIVED + + + + @@ -1145,14 +1171,6 @@ - - - - Adyen\Payment\Gateway\Response\PaymentAuthorisationDetailsHandler - Adyen\Payment\Gateway\Response\PaymentCommentHistoryHandler - - - @@ -1237,7 +1255,6 @@ - Adyen\Payment\Gateway\Request\MerchantAccountDataBuilder Adyen\Payment\Gateway\Request\CaptureDataBuilder Adyen\Payment\Gateway\Request\Header\HeaderDataBuilder @@ -1246,7 +1263,6 @@ - Adyen\Payment\Gateway\Request\MotoMerchantAccountDataBuilder Adyen\Payment\Gateway\Request\MotoClientConfigBuilder Adyen\Payment\Gateway\Request\CaptureDataBuilder @@ -1290,13 +1306,32 @@ - Adyen\Payment\Gateway\Response\CheckoutPaymentsDetailsHandler - Adyen\Payment\Gateway\Response\VaultDetailsHandler - Adyen\Payment\Gateway\Response\CheckoutPaymentCommentHistoryHandler - Adyen\Payment\Gateway\Response\StateDataHandler + Adyen\Payment\Gateway\Response\CheckoutPaymentsResponseHandler + AdyenPaymentAuthorisationOrderStatusHistoryHandler + Adyen\Payment\Gateway\Response\CheckoutStateDataCleanupHandler + + + authorisation + + + + + capture + + + + + refund + + + + + cancellation + + @@ -1307,24 +1342,24 @@ - Adyen\Payment\Gateway\Response\PaymentCaptureDetailsHandler - Adyen\Payment\Gateway\Response\PaymentCommentHistoryHandler + Adyen\Payment\Gateway\Response\ModificationsCapturesResponseHandler + AdyenPaymentCaptureOrderStatusHistoryHandler - Adyen\Payment\Gateway\Response\PaymentRefundDetailsHandler - Adyen\Payment\Gateway\Response\PaymentCommentHistoryRefundHandler + Adyen\Payment\Gateway\Response\ModificationsRefundsResponseHandler + AdyenPaymentRefundOrderStatusHistoryHandler - Adyen\Payment\Gateway\Response\PaymentCancelDetailsHandler - Adyen\Payment\Gateway\Response\PaymentCommentHistoryHandler + Adyen\Payment\Gateway\Response\ModificationsCancelsResponseHandler + AdyenPaymentCancellationOrderStatusHistoryHandler @@ -1333,7 +1368,7 @@ AdyenCcCountryValidator - Adyen\Payment\Gateway\Validator\InstallmentValidator + Adyen\Payment\Gateway\Validator\InstallmentRequestValidator @@ -1341,7 +1376,7 @@ AdyenCcCountryValidator - Adyen\Payment\Gateway\Validator\InstallmentValidator + Adyen\Payment\Gateway\Validator\InstallmentRequestValidator @@ -1354,7 +1389,7 @@ AdyenOneclickCountryValidator - Adyen\Payment\Gateway\Validator\InstallmentValidator + Adyen\Payment\Gateway\Validator\InstallmentRequestValidator @@ -1390,22 +1425,10 @@ - Adyen\Payment\Gateway\Validator\PayByLinkValidator + Adyen\Payment\Gateway\Validator\PaymentLinksRequestValidator - - - - Adyen\Payment\Logger\AdyenLogger - - - - - - Adyen\Payment\Logger\AdyenLogger - - Adyen\Payment\Logger\AdyenLogger @@ -1491,7 +1514,7 @@ Magento\Framework\Serialize\Serializer\Serialize - + Magento\Framework\Serialize\Serializer\Serialize