From 9738ce234fd0c0acfcbfdad024b08820f3051e4d Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 23 Feb 2026 16:56:38 +0100 Subject: [PATCH 1/8] [ECP-9878] Extract payment authorization logic to a separate class --- Helper/AdyenOrderPayment.php | 30 ++-- Helper/Invoice.php | 36 ++-- Helper/Order.php | 41 +++-- .../Webhook/AuthorisationWebhookHandler.php | 136 ++------------- Model/AuthorizationHandler.php | 162 ++++++++++++++++++ 5 files changed, 235 insertions(+), 170 deletions(-) create mode 100644 Model/AuthorizationHandler.php diff --git a/Helper/AdyenOrderPayment.php b/Helper/AdyenOrderPayment.php index d8c2336d6d..a3bb4ae7c4 100644 --- a/Helper/AdyenOrderPayment.php +++ b/Helper/AdyenOrderPayment.php @@ -192,22 +192,32 @@ public function isFullAmountAuthorized(Order $order): bool } /** - * Create an entry in the adyen_order_payment table based on the passed notification + * Create an entry in the adyen_order_payment table based on the provided details * * @param Order $order - * @param Notification $notification * @param bool $autoCapture + * @param string $merchantReference + * @param string $pspReference + * @param string $paymentMethod + * @param int $amountValue + * @param string $amountCurrency * @return Payment|null */ - public function createAdyenOrderPayment(Order $order, Notification $notification, bool $autoCapture): ?Payment - { + public function createAdyenOrderPayment( + Order $order, + bool $autoCapture, + string $merchantReference, + string $pspReference, + string $paymentMethod, + int $amountValue, + string $amountCurrency + ): ?Payment { $adyenOrderPayment = null; $payment = $order->getPayment(); - $amount = $this->adyenDataHelper->originalAmount($notification->getAmountValue(), - $notification->getAmountCurrency()); - $captureStatus = $autoCapture ? Payment::CAPTURE_STATUS_AUTO_CAPTURE : Payment::CAPTURE_STATUS_NO_CAPTURE; - $merchantReference = $notification->getMerchantReference(); - $pspReference = $notification->getPspreference(); + $amount = $this->adyenDataHelper->originalAmount($amountValue, $amountCurrency); + $captureStatus = $autoCapture ? + OrderPaymentInterface::CAPTURE_STATUS_AUTO_CAPTURE : + OrderPaymentInterface::CAPTURE_STATUS_NO_CAPTURE; try { $date = new \DateTime(); @@ -215,7 +225,7 @@ public function createAdyenOrderPayment(Order $order, Notification $notification $adyenOrderPayment->setPspreference($pspReference); $adyenOrderPayment->setMerchantReference($merchantReference); $adyenOrderPayment->setPaymentId($payment->getId()); - $adyenOrderPayment->setPaymentMethod($notification->getPaymentMethod()); + $adyenOrderPayment->setPaymentMethod($paymentMethod); $adyenOrderPayment->setCaptureStatus($captureStatus); $adyenOrderPayment->setAmount($amount); $adyenOrderPayment->setTotalRefunded(0); diff --git a/Helper/Invoice.php b/Helper/Invoice.php index 1c89b72cc1..a204210ba0 100644 --- a/Helper/Invoice.php +++ b/Helper/Invoice.php @@ -77,18 +77,25 @@ public function __construct( /** * @param Order $order - * @param Notification $notification * @param bool $isAutoCapture + * @param string $pspReference + * @param string $merchantReference + * @param int $amountValue * @return InvoiceModel|null * @throws LocalizedException */ - public function createInvoice(Order $order, Notification $notification, bool $isAutoCapture): ?InvoiceModel - { + public function createInvoice( + Order $order, + bool $isAutoCapture, + string $pspReference, + string $merchantReference, + int $amountValue + ): ?InvoiceModel { $this->adyenLogger->addAdyenNotification( 'Creating invoice for order', [ - 'pspReference' => $notification->getPspreference(), - 'merchantReference' => $notification->getMerchantReference() + 'pspReference' => $pspReference, + 'merchantReference' => $merchantReference ] ); @@ -102,12 +109,12 @@ public function createInvoice(Order $order, Notification $notification, bool $is $invoice->getOrder()->setIsInProcess(true); // set transaction id so you can do a online refund from credit memo - $invoice->setTransactionId($notification->getPspreference()); + $invoice->setTransactionId($pspReference); if ((!$isAutoCapture)) { // if amount is zero create a offline invoice - $value = (int)$notification->getAmountValue(); + $value = $amountValue; if ($value == 0) { $invoice->setRequestedCaptureCase(InvoiceModel::CAPTURE_OFFLINE); } else { @@ -121,10 +128,9 @@ public function createInvoice(Order $order, Notification $notification, bool $is $this->invoiceRepository->save($invoice); $this->adyenLogger->addAdyenNotification(sprintf( - 'Notification %s created an invoice for order with pspReference %s and merchantReference %s', - $notification->getEntityId(), - $notification->getPspreference(), - $notification->getMerchantReference() + 'An invoice was generated for order with pspReference %s and merchantReference %s', + $pspReference, + $merchantReference ), $this->adyenLogger->getInvoiceContext($invoice) ); @@ -132,8 +138,8 @@ public function createInvoice(Order $order, Notification $notification, bool $is $this->adyenLogger->addAdyenNotification( 'Error saving invoice: ' . $e->getMessage(), [ - 'pspReference' => $notification->getPspreference(), - 'merchantReference' => $notification->getMerchantReference() + 'pspReference' => $pspReference, + 'merchantReference' => $merchantReference ] ); @@ -153,9 +159,9 @@ public function createInvoice(Order $order, Notification $notification, bool $is return $invoice; } else { $this->adyenLogger->addAdyenNotification( - sprintf('Unable to create invoice when handling Notification %s', $notification->getEntityId()), + __('Unable to create invoice when handling notification'), array_merge($this->adyenLogger->getOrderContext($order), [ - 'pspReference' => $notification->getPspReference(), + 'pspReference' => $pspReference, 'canUnhold' => $order->canUnhold(), 'isPaymentReview' => $order->isPaymentReview(), 'isCancelled' => $order->isCanceled(), diff --git a/Helper/Order.php b/Helper/Order.php index a416bbc3ab..bded475240 100644 --- a/Helper/Order.php +++ b/Helper/Order.php @@ -86,11 +86,11 @@ public function __construct( /** * @param MagentoOrder $order - * @param Notification $notification + * @param string $pspReference * @return TransactionInterface|null * @throws Exception */ - public function updatePaymentDetails(MagentoOrder $order, Notification $notification): ?TransactionInterface + public function updatePaymentDetails(MagentoOrder $order, string $pspReference): ?TransactionInterface { //Set order state to new because with order state payment_review it is not possible to create an invoice if (strcmp($order->getState(), MagentoOrder::STATE_PAYMENT_REVIEW) == 0) { @@ -100,15 +100,15 @@ public function updatePaymentDetails(MagentoOrder $order, Notification $notifica $paymentObj = $order->getPayment(); // set pspReference as transactionId - $paymentObj->setCcTransId($notification->getPspreference()); - $paymentObj->setLastTransId($notification->getPspreference()); + $paymentObj->setCcTransId($pspReference); + $paymentObj->setLastTransId($pspReference); // set transaction - $paymentObj->setTransactionId($notification->getPspreference()); + $paymentObj->setTransactionId($pspReference); // Prepare transaction $transaction = $this->transactionBuilder->setPayment($paymentObj) ->setOrder($order) - ->setTransactionId($notification->getPspreference()) + ->setTransactionId($pspReference) ->build(TransactionInterface::TYPE_AUTH); $transaction->setIsClosed(false); @@ -198,12 +198,17 @@ public function createShipment(MagentoOrder $order): MagentoOrder * Full order will only NOT be finalized if the full amount has not been captured/authorized. * * @param MagentoOrder $order - * @param Notification $notification + * @param string $pspReference + * @param string $merchantReference + * @param int $amount * @return MagentoOrder */ - public function finalizeOrder(MagentoOrder $order, Notification $notification): MagentoOrder - { - $amount = $notification->getAmountValue(); + public function finalizeOrder( + MagentoOrder $order, + string $pspReference, + string $merchantReference, + int $amount + ): MagentoOrder { $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order, false); $formattedOrderAmount = $this->dataHelper->formatAmount($orderAmountCurrency->getAmount(), $orderAmountCurrency->getCurrencyCode()); $fullAmountFinalized = $this->adyenOrderPaymentHelper->isFullAmountFinalized($order); @@ -221,12 +226,6 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification): $status = $this->getVirtualStatus($order, $status); } - // check for boleto if payment is totally paid - if ($order->getPayment()->getMethod() == "adyen_boleto") { - $status = $this->paymentMethodsHelper->getBoletoStatus($order, $notification, $status); - } - - $order = $this->addProcessedStatusHistoryComment($order, $notification); if ($fullAmountFinalized) { $this->adyenLogger->addAdyenNotification(sprintf( 'Notification w/amount %s has completed the capturing of order %s w/amount %s', @@ -235,8 +234,8 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification): $formattedOrderAmount ), [ - 'pspReference' => $notification->getPspreference(), - 'merchantReference' => $notification->getMerchantReference() + 'pspReference' => $pspReference, + 'merchantReference' => $merchantReference ]); $comment = "Adyen Payment Successfully completed"; // If a status is set, add comment, set status and update the state based on the status @@ -248,7 +247,7 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification): 'Order status was changed to authorised status: ' . $status, array_merge( $this->adyenLogger->getOrderContext($order), - ['pspReference' => $notification->getPspreference()] + ['pspReference' => $pspReference] ) ); } else { @@ -258,8 +257,8 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification): $order->getIncrementId() ), [ - 'pspReference' => $notification->getPspreference(), - 'merchantReference' => $notification->getMerchantReference() + 'pspReference' => $pspReference, + 'merchantReference' => $merchantReference ]); } } else { diff --git a/Helper/Webhook/AuthorisationWebhookHandler.php b/Helper/Webhook/AuthorisationWebhookHandler.php index c5581daf03..094a908824 100644 --- a/Helper/Webhook/AuthorisationWebhookHandler.php +++ b/Helper/Webhook/AuthorisationWebhookHandler.php @@ -14,48 +14,36 @@ use Adyen\Payment\Api\CleanupAdditionalInformationInterface; use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; -use Adyen\Payment\Helper\AdyenOrderPayment; -use Adyen\Payment\Helper\CaseManagement; +use Adyen\Payment\Model\AuthorizationHandler; use Adyen\Payment\Helper\Config; -use Adyen\Payment\Helper\Invoice; use Adyen\Payment\Helper\Order as OrderHelper; -use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\Notification; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; use Adyen\Webhook\PaymentStates; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Serialize\SerializerInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Sales\Model\Order; class AuthorisationWebhookHandler implements WebhookHandlerInterface { /** - * @param AdyenOrderPayment $adyenOrderPaymentHelper * @param OrderHelper $orderHelper - * @param CaseManagement $caseManagementHelper - * @param SerializerInterface $serializer * @param AdyenLogger $adyenLogger * @param Config $configHelper - * @param Invoice $invoiceHelper - * @param PaymentMethods $paymentMethodsHelper * @param CartRepositoryInterface $cartRepository * @param AdyenNotificationRepositoryInterface $notificationRepository * @param CleanupAdditionalInformationInterface $cleanupAdditionalInformation + * @param AuthorizationHandler $authorizationHandler */ public function __construct( - private readonly AdyenOrderPayment $adyenOrderPaymentHelper, private readonly OrderHelper $orderHelper, - private readonly CaseManagement $caseManagementHelper, - private readonly SerializerInterface $serializer, private readonly AdyenLogger $adyenLogger, private readonly Config $configHelper, - private readonly Invoice $invoiceHelper, - private readonly PaymentMethods $paymentMethodsHelper, private readonly CartRepositoryInterface $cartRepository, private readonly AdyenNotificationRepositoryInterface $notificationRepository, - private readonly CleanupAdditionalInformationInterface $cleanupAdditionalInformation + private readonly CleanupAdditionalInformationInterface $cleanupAdditionalInformation, + private readonly AuthorizationHandler $authorizationHandler ) { } /** @@ -84,78 +72,22 @@ public function handleWebhook(Order $order, Notification $notification, string $ */ private function handleSuccessfulAuthorisation(Order $order, Notification $notification): Order { - $paymentMethod = (string) $notification->getPaymentMethod(); - $isAutoCapture = $this->paymentMethodsHelper->isAutoCapture($order, $paymentMethod); + $paymentMethod = $notification->getPaymentMethod(); + $pspReference = $notification->getPspReference(); + $merchantReference = $notification->getMerchantReference(); + $amountValue = $notification->getAmountValue(); + $amountCurrency = $notification->getAmountCurrency(); - $this->markPaymentCapturedIfNeeded($order, $notification, $isAutoCapture); - - $this->adyenOrderPaymentHelper->createAdyenOrderPayment($order, $notification, $isAutoCapture); - - if (!$this->adyenOrderPaymentHelper->isFullAmountAuthorized($order)) { - $this->orderHelper->addWebhookStatusHistoryComment($order, $notification); - $this->createCashShipmentIfNeeded($order, $paymentMethod); - $this->deactivateQuoteIfNeeded($order); - return $order; - } - - $order = $this->orderHelper->setPrePaymentAuthorized($order); - $this->orderHelper->updatePaymentDetails($order, $notification); - - $additionalData = $this->getAdditionalDataArray($notification); - $requireFraudManualReview = $this->caseManagementHelper->requiresManualReview($additionalData); - - $order = $isAutoCapture - ? $this->handleAutoCapture($order, $notification, $requireFraudManualReview) - : $this->handleManualCapture($order, $notification, $requireFraudManualReview); - - $this->sendOrderMailIfNeeded($order, $paymentMethod); + $order = $this->authorizationHandler->execute($order, $paymentMethod, $merchantReference, $pspReference, $amountValue, $amountCurrency, $notification); $payment = $order->getPayment(); - $payment->setAmountAuthorized($order->getGrandTotal()); - $payment->setBaseAmountAuthorized($order->getBaseGrandTotal()); - - $this->cleanupAdditionalInformation->execute($payment); - - $this->createCashShipmentIfNeeded($order, $paymentMethod); $this->deactivateQuoteIfNeeded($order); + $this->cleanupAdditionalInformation->execute($payment); + $this->orderHelper->addWebhookStatusHistoryComment($order, $notification); return $order; } - private function markPaymentCapturedIfNeeded(Order $order, Notification $notification, bool $isAutoCapture): void - { - // Set adyen_notification_payment_captured to true so that we ignore a possible OFFER_CLOSED - if ($notification->isSuccessful() && $isAutoCapture) { - $order->setData('adyen_notification_payment_captured', 1); - } - } - - private function getAdditionalDataArray(Notification $notification): array - { - $raw = $notification->getAdditionalData(); - return !empty($raw) ? (array) $this->serializer->unserialize($raw) : []; - } - - private function sendOrderMailIfNeeded(Order $order, string $paymentMethod): void - { - // For Boleto confirmation mail is sent on order creation - // Send order confirmation mail after invoice creation so merchant can add invoicePDF to this mail - if ($paymentMethod !== 'adyen_boleto' && !$order->getEmailSent()) { - $this->orderHelper->sendOrderMail($order); - } - } - - private function createCashShipmentIfNeeded(Order $order, string $paymentMethod): void - { - if ($paymentMethod !== 'c_cash') { - return; - } - - if ($this->configHelper->getConfigData('create_shipment', 'adyen_cash', $order->getStoreId())) { - $this->orderHelper->createShipment($order); - } - } - private function deactivateQuoteIfNeeded(Order $order): void { $quoteId = $order->getQuoteId(); @@ -241,50 +173,6 @@ private function handleFailedAuthorisation(Order $order, Notification $notificat return $this->orderHelper->holdCancelOrder($order, true); } - /** - * @param Order $order - * @param Notification $notification - * @param bool $requireFraudManualReview - * @return Order - * @throws LocalizedException - */ - private function handleAutoCapture(Order $order, Notification $notification, bool $requireFraudManualReview): Order - { - $this->invoiceHelper->createInvoice($order, $notification, true); - if ($requireFraudManualReview) { - $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $notification->getPspreference(), true); - } else { - $order = $this->orderHelper->finalizeOrder($order, $notification); - } - - return $order; - } - - /** - * @param Order $order - * @param Notification $notification - * @param bool $requireFraudManualReview - * @return Order - */ - private function handleManualCapture(Order $order, Notification $notification, bool $requireFraudManualReview): Order - { - if ($requireFraudManualReview) { - $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $notification->getPspreference(), false); - } else { - $order = $this->orderHelper->addWebhookStatusHistoryComment($order, $notification); - $order->addStatusHistoryComment(__('Capture Mode set to Manual'), $order->getStatus()); - $this->adyenLogger->addAdyenNotification( - 'Capture mode is set to Manual', - [ - 'pspReference' =>$notification->getPspreference(), - 'merchantReference' => $notification->getMerchantReference() - ] - ); - } - - return $order; - } - /** * @param Order $order * @param Notification $notification diff --git a/Model/AuthorizationHandler.php b/Model/AuthorizationHandler.php new file mode 100644 index 0000000000..d19ca5700e --- /dev/null +++ b/Model/AuthorizationHandler.php @@ -0,0 +1,162 @@ +paymentMethodsHelper->isAutoCapture($order, $paymentMethod); + + $this->adyenOrderPaymentHelper->createAdyenOrderPayment( + $order, + $isAutoCapture, + $merchantReference, + $pspReference, + $paymentMethod, + $amountValue, + $amountCurrency + ); + + if ($this->adyenOrderPaymentHelper->isFullAmountAuthorized($order)) { + $order = $this->orderHelper->setPrePaymentAuthorized($order); + $this->orderHelper->updatePaymentDetails($order, $pspReference); + + $additionalData = $this->getAdditionalDataArray($notification); + $requireFraudManualReview = $this->caseManagementHelper->requiresManualReview($additionalData); + + $order = $isAutoCapture + ? $this->handleAutoCapture( + $order, + $pspReference, + $merchantReference, + $amountValue, + $requireFraudManualReview + ) + : $this->handleManualCapture($order, $pspReference, $merchantReference, $requireFraudManualReview); + + $this->sendOrderMailIfNeeded($order, $paymentMethod); + + $payment = $order->getPayment(); + $payment->setAmountAuthorized($order->getGrandTotal()); + $payment->setBaseAmountAuthorized($order->getBaseGrandTotal()); + + if ($isAutoCapture) { + $order->setData('adyen_notification_payment_captured', 1); + } + } + return $order; + } + + /** + * @param Order $order + * @param string $pspReference + * @param string $merchantReference + * @param int $amountValue + * @param bool $requireFraudManualReview + * @return Order + * @throws LocalizedException + */ + private function handleAutoCapture( + Order $order, + string $pspReference, + string $merchantReference, + int $amountValue, + bool $requireFraudManualReview + ): Order + { + $this->invoiceHelper->createInvoice($order, true, $pspReference, $merchantReference, $amountValue); + if ($requireFraudManualReview) { + $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference, true); + } else { + $order = $this->orderHelper->finalizeOrder($order, $pspReference, $merchantReference, $amountValue); + } + + return $order; + } + + /** + * @param Order $order + * @param string $pspReference + * @param string $merchantReference + * @param bool $requireFraudManualReview + * @return Order + */ + private function handleManualCapture( + Order $order, + string $pspReference, + string $merchantReference, + bool $requireFraudManualReview + ): Order + { + if ($requireFraudManualReview) { + $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference); + } else { + $order->addCommentToStatusHistory(__('Capture Mode set to Manual'), $order->getStatus()); + $this->adyenLogger->addAdyenNotification( + 'Capture mode is set to Manual', + [ + 'pspReference' => $pspReference, + 'merchantReference' => $merchantReference + ] + ); + } + + return $order; + } + + private function sendOrderMailIfNeeded(Order $order, string $paymentMethod): void + { + // For Boleto confirmation mail is sent on order creation + // Send order confirmation mail after invoice creation so merchant can add invoicePDF to this mail + if ($paymentMethod !== 'adyen_boleto' && !$order->getEmailSent()) { + $this->orderHelper->sendOrderMail($order); + } + } + + private function getAdditionalDataArray(Notification $notification): array + { + $raw = $notification->getAdditionalData(); + return !empty($raw) ? (array) $this->serializer->unserialize($raw) : []; + } +} From 5876c0e211122ce63841eda51c9bbc14989acbed Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 23 Feb 2026 16:56:54 +0100 Subject: [PATCH 2/8] [ECP-9878] Remove getBoletoStatus() method --- Helper/PaymentMethods.php | 53 ---------------------- Test/Unit/Helper/PaymentMethodsTest.php | 59 ------------------------- 2 files changed, 112 deletions(-) diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php index 6bc5fa2141..fb1e7abcc0 100644 --- a/Helper/PaymentMethods.php +++ b/Helper/PaymentMethods.php @@ -943,59 +943,6 @@ public function isBankTransfer(string $paymentMethod): bool return $isBankTransfer; } - /** - * @param Order $order - * @param Notification $notification - * @param string $status - * @return string|null - */ - public function getBoletoStatus(Order $order, Notification $notification, string $status): ?string - { - $additionalData = !empty($notification->getAdditionalData()) ? $this->serializer->unserialize( - $notification->getAdditionalData() - ) : ""; - - $boletobancario = $additionalData['boletobancario'] ?? null; - if ($boletobancario && is_array($boletobancario)) { - // check if paid amount is the same as orginal amount - $originalAmount = - isset($boletobancario['originalAmount']) ? - trim((string) $boletobancario['originalAmount']) : - ""; - $paidAmount = isset($boletobancario['paidAmount']) ? trim((string) $boletobancario['paidAmount']) : ""; - - if ($originalAmount != $paidAmount) { - // not the full amount is paid. Check if it is underpaid or overpaid - // strip the BRL of the string - $originalAmount = str_replace("BRL", "", $originalAmount); - $originalAmount = floatval(trim($originalAmount)); - - $paidAmount = str_replace("BRL", "", $paidAmount); - $paidAmount = floatval(trim($paidAmount)); - - if ($paidAmount > $originalAmount) { - $overpaidStatus = $this->configHelper->getConfigData( - 'order_overpaid_status', - 'adyen_boleto', - $order->getStoreId() - ); - // check if there is selected a status if not fall back to the default - $status = (!empty($overpaidStatus)) ? $overpaidStatus : $status; - } else { - $underpaidStatus = $this->configHelper->getConfigData( - 'order_underpaid_status', - 'adyen_boleto', - $order->getStoreId() - ); - // check if there is selected a status if not fall back to the default - $status = (!empty($underpaidStatus)) ? $underpaidStatus : $status; - } - } - } - - return $status; - } - /** * @param string $paymentMethodCode * @param array $params diff --git a/Test/Unit/Helper/PaymentMethodsTest.php b/Test/Unit/Helper/PaymentMethodsTest.php index 2509583b65..456bb9bd97 100644 --- a/Test/Unit/Helper/PaymentMethodsTest.php +++ b/Test/Unit/Helper/PaymentMethodsTest.php @@ -351,30 +351,6 @@ public function testTogglePaymentMethodsActivation_Disable(): void $this->assertNotContains(AdyenMotoConfigProvider::CODE, $result); } - public function testGetBoletoStatusOverpaid(): void - { - $order = $this->createConfiguredMock(\Magento\Sales\Model\Order::class, ['getStoreId' => 1]); - $notification = $this->createMock(\Adyen\Payment\Model\Notification::class); - - $additionalData = ['boletobancario' => [ - 'originalAmount' => 'BRL 100.00', - 'paidAmount' => 'BRL 120.00' - ]]; - - $this->serializer - ->method('unserialize') - ->willReturn($additionalData); - - $notification->method('getAdditionalData')->willReturn(json_encode($additionalData)); - $this->configHelper - ->method('getConfigData') - ->with('order_overpaid_status', 'adyen_boleto', 1) - ->willReturn('overpaid_status'); - - $status = $this->helper->getBoletoStatus($order, $notification, 'default_status'); - $this->assertEquals('overpaid_status', $status); - } - /** * @dataProvider autoCaptureDataProvider */ @@ -455,27 +431,6 @@ public function testCompareOrderAndWebhookPaymentMethodsAlternativeMatch(): void $this->assertTrue($this->helper->compareOrderAndWebhookPaymentMethods($order, $notification)); } - public function testGetBoletoStatusUnderpaid(): void - { - $order = $this->createConfiguredMock(\Magento\Sales\Model\Order::class, ['getStoreId' => 1]); - $notification = $this->createMock(\Adyen\Payment\Model\Notification::class); - - $additionalData = ['boletobancario' => [ - 'originalAmount' => 'BRL 100.00', - 'paidAmount' => 'BRL 80.00' - ]]; - - $this->serializer->method('unserialize')->willReturn($additionalData); - $notification->method('getAdditionalData')->willReturn(json_encode($additionalData)); - - $this->configHelper->method('getConfigData') - ->with('order_underpaid_status', 'adyen_boleto', 1) - ->willReturn('underpaid_status'); - - $status = $this->helper->getBoletoStatus($order, $notification, 'default_status'); - $this->assertEquals('underpaid_status', $status); - } - /** * @dataProvider comparePaymentMethodProvider */ @@ -894,20 +849,6 @@ public function testPaymentMethodSupportsRecurringFalse(): void $this->assertFalse($this->helper->paymentMethodSupportsRecurring($this->methodMock)); } - public function testGetBoletoStatusWithMissingAmounts(): void - { - $order = $this->createConfiguredMock(\Magento\Sales\Model\Order::class, ['getStoreId' => 1]); - $notification = $this->createMock(\Adyen\Payment\Model\Notification::class); - - $data = ['boletobancario' => []]; - - $this->serializer->method('unserialize')->willReturn($data); - $notification->method('getAdditionalData')->willReturn(json_encode($data)); - - $result = $this->helper->getBoletoStatus($order, $notification, 'default'); - $this->assertEquals('default', $result); - } - public function testGetPaymentMethodsRequestAddsShopperConversionIdWhenPresent() { $merchantAccount = 'TestMerchant'; From 37e049719ae03e2395108e9618ed67da70ed608e Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 10:53:36 +0100 Subject: [PATCH 3/8] [ECP-9878] Promote order to authorised after sync API response --- Helper/AdyenOrderPayment.php | 6 +- Helper/Invoice.php | 8 +- Helper/Order.php | 6 +- Helper/PaymentResponseHandler.php | 15 ++++ .../Webhook/AuthorisationWebhookHandler.php | 54 ++++++++---- Helper/Webhook/CaptureWebhookHandler.php | 6 +- .../ManualReviewAcceptWebhookHandler.php | 6 +- Model/AuthorizationHandler.php | 78 ++++++++-------- Observer/AuthorizeAfterOrderPlacement.php | 88 +++++++++++++++++++ etc/events.xml | 3 + 10 files changed, 198 insertions(+), 72 deletions(-) create mode 100644 Observer/AuthorizeAfterOrderPlacement.php diff --git a/Helper/AdyenOrderPayment.php b/Helper/AdyenOrderPayment.php index a3bb4ae7c4..e372cbc9fa 100644 --- a/Helper/AdyenOrderPayment.php +++ b/Helper/AdyenOrderPayment.php @@ -196,7 +196,6 @@ public function isFullAmountAuthorized(Order $order): bool * * @param Order $order * @param bool $autoCapture - * @param string $merchantReference * @param string $pspReference * @param string $paymentMethod * @param int $amountValue @@ -206,7 +205,6 @@ public function isFullAmountAuthorized(Order $order): bool public function createAdyenOrderPayment( Order $order, bool $autoCapture, - string $merchantReference, string $pspReference, string $paymentMethod, int $amountValue, @@ -223,7 +221,7 @@ public function createAdyenOrderPayment( $date = new \DateTime(); $adyenOrderPayment = $this->adyenOrderPaymentFactory->create(); $adyenOrderPayment->setPspreference($pspReference); - $adyenOrderPayment->setMerchantReference($merchantReference); + $adyenOrderPayment->setMerchantReference($order->getIncrementId()); $adyenOrderPayment->setPaymentId($payment->getId()); $adyenOrderPayment->setPaymentMethod($paymentMethod); $adyenOrderPayment->setCaptureStatus($captureStatus); @@ -238,7 +236,7 @@ public function createAdyenOrderPayment( 'adyen_order_payment table but something went wrong later. Please check your logs for potential ' . 'error messages regarding the merchant reference (order id): %s and PSP reference: %s. ' . 'Exception message: %s', - $merchantReference, + $order->getIncrementId(), $pspReference, $e->getMessage() )); diff --git a/Helper/Invoice.php b/Helper/Invoice.php index a204210ba0..30725e7f2f 100644 --- a/Helper/Invoice.php +++ b/Helper/Invoice.php @@ -79,7 +79,6 @@ public function __construct( * @param Order $order * @param bool $isAutoCapture * @param string $pspReference - * @param string $merchantReference * @param int $amountValue * @return InvoiceModel|null * @throws LocalizedException @@ -88,14 +87,13 @@ public function createInvoice( Order $order, bool $isAutoCapture, string $pspReference, - string $merchantReference, int $amountValue ): ?InvoiceModel { $this->adyenLogger->addAdyenNotification( 'Creating invoice for order', [ 'pspReference' => $pspReference, - 'merchantReference' => $merchantReference + 'merchantReference' => $order->getIncrementId() ] ); @@ -130,7 +128,7 @@ public function createInvoice( $this->adyenLogger->addAdyenNotification(sprintf( 'An invoice was generated for order with pspReference %s and merchantReference %s', $pspReference, - $merchantReference + $order->getIncrementId() ), $this->adyenLogger->getInvoiceContext($invoice) ); @@ -139,7 +137,7 @@ public function createInvoice( 'Error saving invoice: ' . $e->getMessage(), [ 'pspReference' => $pspReference, - 'merchantReference' => $merchantReference + 'merchantReference' => $order->getIncrementId() ] ); diff --git a/Helper/Order.php b/Helper/Order.php index bded475240..686f232ca5 100644 --- a/Helper/Order.php +++ b/Helper/Order.php @@ -199,14 +199,12 @@ public function createShipment(MagentoOrder $order): MagentoOrder * * @param MagentoOrder $order * @param string $pspReference - * @param string $merchantReference * @param int $amount * @return MagentoOrder */ public function finalizeOrder( MagentoOrder $order, string $pspReference, - string $merchantReference, int $amount ): MagentoOrder { $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order, false); @@ -235,7 +233,7 @@ public function finalizeOrder( ), [ 'pspReference' => $pspReference, - 'merchantReference' => $merchantReference + 'merchantReference' => $order->getIncrementId() ]); $comment = "Adyen Payment Successfully completed"; // If a status is set, add comment, set status and update the state based on the status @@ -258,7 +256,7 @@ public function finalizeOrder( ), [ 'pspReference' => $pspReference, - 'merchantReference' => $merchantReference + 'merchantReference' => $order->getIncrementId() ]); } } else { diff --git a/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php index 5ef5053f19..b2eea2c39f 100644 --- a/Helper/PaymentResponseHandler.php +++ b/Helper/PaymentResponseHandler.php @@ -12,6 +12,7 @@ namespace Adyen\Payment\Helper; use Adyen\Payment\Helper\Order as AdyenOrderHelper; +use Adyen\Payment\Model\AuthorizationHandler; use Adyen\Payment\Logger\AdyenLogger; use Exception; use Magento\Framework\Exception\AlreadyExistsException; @@ -59,6 +60,7 @@ class PaymentResponseHandler * @param PaymentMethods $paymentMethodsHelper * @param OrderStatusHistory $orderStatusHistoryHelper * @param OrdersApi $ordersApiHelper + * @param AuthorizationHandler $authorizationHandler */ public function __construct( private readonly AdyenLogger $adyenLogger, @@ -70,6 +72,7 @@ public function __construct( private readonly PaymentMethods $paymentMethodsHelper, private readonly OrderStatusHistory $orderStatusHistoryHelper, private readonly OrdersApi $ordersApiHelper, + private readonly AuthorizationHandler $authorizationHandler, ) { } public function formatPaymentResponse( @@ -313,6 +316,18 @@ public function handlePaymentsDetailsResponse( break; } + // Authorize the payment based on the result code + if ($resultCode === self::AUTHORISED) { + $order = $this->authorizationHandler->execute( + $order, + $paymentMethod, + $paymentsDetailsResponse['pspReference'], + intval($paymentsDetailsResponse['amount']['value']), + $paymentsDetailsResponse['amount']['currency'], + $paymentsDetailsResponse['additionalData'] ?? [] + ); + } + // needed because then we need to save $order objects $order->setAdyenResulturlEventCode($authResult); $this->orderRepository->save($order); diff --git a/Helper/Webhook/AuthorisationWebhookHandler.php b/Helper/Webhook/AuthorisationWebhookHandler.php index 094a908824..88caef0fa2 100644 --- a/Helper/Webhook/AuthorisationWebhookHandler.php +++ b/Helper/Webhook/AuthorisationWebhookHandler.php @@ -19,13 +19,15 @@ use Adyen\Payment\Helper\Order as OrderHelper; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\Notification; +use Adyen\Payment\Model\ResourceModel\Order\Payment as AdyenOrderPayment; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; use Adyen\Webhook\PaymentStates; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\SerializerInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Sales\Model\Order; -class AuthorisationWebhookHandler implements WebhookHandlerInterface +readonly class AuthorisationWebhookHandler implements WebhookHandlerInterface { /** * @param OrderHelper $orderHelper @@ -35,15 +37,19 @@ class AuthorisationWebhookHandler implements WebhookHandlerInterface * @param AdyenNotificationRepositoryInterface $notificationRepository * @param CleanupAdditionalInformationInterface $cleanupAdditionalInformation * @param AuthorizationHandler $authorizationHandler + * @param SerializerInterface $serializer + * @param AdyenOrderPayment $adyenOrderPaymentResourceModel */ public function __construct( - private readonly OrderHelper $orderHelper, - private readonly AdyenLogger $adyenLogger, - private readonly Config $configHelper, - private readonly CartRepositoryInterface $cartRepository, - private readonly AdyenNotificationRepositoryInterface $notificationRepository, - private readonly CleanupAdditionalInformationInterface $cleanupAdditionalInformation, - private readonly AuthorizationHandler $authorizationHandler + private OrderHelper $orderHelper, + private AdyenLogger $adyenLogger, + private Config $configHelper, + private CartRepositoryInterface $cartRepository, + private AdyenNotificationRepositoryInterface $notificationRepository, + private CleanupAdditionalInformationInterface $cleanupAdditionalInformation, + private AuthorizationHandler $authorizationHandler, + private SerializerInterface $serializer, + private AdyenOrderPayment $adyenOrderPaymentResourceModel ) { } /** @@ -72,15 +78,27 @@ public function handleWebhook(Order $order, Notification $notification, string $ */ private function handleSuccessfulAuthorisation(Order $order, Notification $notification): Order { - $paymentMethod = $notification->getPaymentMethod(); - $pspReference = $notification->getPspReference(); - $merchantReference = $notification->getMerchantReference(); - $amountValue = $notification->getAmountValue(); - $amountCurrency = $notification->getAmountCurrency(); + $payment = $order->getPayment(); - $order = $this->authorizationHandler->execute($order, $paymentMethod, $merchantReference, $pspReference, $amountValue, $amountCurrency, $notification); + /* + * Ideally, order payment should be created after getting an `Authorized` resultCode to + * /payments or /payments/details API calls and order should be promoted to `authorized` or `processing` state. + * However, the following block allows to create order payment and order to be promoted during + * webhook handling as a fallback. + */ + if (empty($this->adyenOrderPaymentResourceModel->getOrderPaymentDetails( + $notification->getPspreference(), $payment->getEntityId()) + )) { + $order = $this->authorizationHandler->execute( + $order, + $notification->getPaymentMethod(), + $notification->getPspReference(), + $notification->getAmountValue(), + $notification->getAmountCurrency(), + $this->getAdditionalDataArray($notification) + ); + } - $payment = $order->getPayment(); $this->deactivateQuoteIfNeeded($order); $this->cleanupAdditionalInformation->execute($payment); $this->orderHelper->addWebhookStatusHistoryComment($order, $notification); @@ -216,4 +234,10 @@ private function canCancelPayByLinkOrder(Order $order, Notification $notificatio return false; } + + private function getAdditionalDataArray(Notification $notification): array + { + $raw = $notification->getAdditionalData(); + return !empty($raw) ? (array) $this->serializer->unserialize($raw) : []; + } } diff --git a/Helper/Webhook/CaptureWebhookHandler.php b/Helper/Webhook/CaptureWebhookHandler.php index 0375bf6a5c..5f2f50e26b 100644 --- a/Helper/Webhook/CaptureWebhookHandler.php +++ b/Helper/Webhook/CaptureWebhookHandler.php @@ -101,6 +101,10 @@ public function handleWebhook(MagentoOrder $order, Notification $notification, s ) ); - return $this->orderHelper->finalizeOrder($order, $notification); + return $this->orderHelper->finalizeOrder( + $order, + $notification->getPspreference(), + $notification->getAmountValue() + ); } } diff --git a/Helper/Webhook/ManualReviewAcceptWebhookHandler.php b/Helper/Webhook/ManualReviewAcceptWebhookHandler.php index 76a0ad49f0..96b502e237 100644 --- a/Helper/Webhook/ManualReviewAcceptWebhookHandler.php +++ b/Helper/Webhook/ManualReviewAcceptWebhookHandler.php @@ -48,7 +48,11 @@ public function handleWebhook(MagentoOrder $order, Notification $notification, s // Finalize order only in case of auto capture. For manual capture the capture notification will initiate this finalizeOrder call if ($this->paymentMethodsHelper->isAutoCapture($order, $notification->getPaymentMethod())) { - $order = $this->orderHelper->finalizeOrder($order, $notification); + $order = $this->orderHelper->finalizeOrder( + $order, + $notification->getPspreference(), + $notification->getAmountValue() + ); } return $order; diff --git a/Model/AuthorizationHandler.php b/Model/AuthorizationHandler.php index d19ca5700e..e5e256460e 100644 --- a/Model/AuthorizationHandler.php +++ b/Model/AuthorizationHandler.php @@ -8,50 +8,51 @@ use Adyen\Payment\Helper\Order as OrderHelper; use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; -use Exception; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Serialize\SerializerInterface; -use Magento\Sales\Model\Order; +use Magento\Sales\Api\Data\OrderInterface; readonly class AuthorizationHandler { + /** + * @param AdyenOrderPayment $adyenOrderPaymentHelper + * @param CaseManagement $caseManagementHelper + * @param Invoice $invoiceHelper + * @param PaymentMethods $paymentMethodsHelper + * @param OrderHelper $orderHelper + * @param AdyenLogger $adyenLogger + */ public function __construct( private AdyenOrderPayment $adyenOrderPaymentHelper, private CaseManagement $caseManagementHelper, private Invoice $invoiceHelper, private PaymentMethods $paymentMethodsHelper, private OrderHelper $orderHelper, - private AdyenLogger $adyenLogger, - private SerializerInterface $serializer + private AdyenLogger $adyenLogger ) { } /** - * @param Order $order + * @param OrderInterface $order * @param string $paymentMethod - * @param string $merchantReference * @param string $pspReference * @param int $amountValue * @param string $amountCurrency - * @param Notification $notification - * @return Order + * @param array $additionalData + * @return OrderInterface * @throws LocalizedException - * @throws Exception */ public function execute( - Order $order, + OrderInterface $order, string $paymentMethod, - string $merchantReference, string $pspReference, int $amountValue, string $amountCurrency, - Notification $notification - ): Order { + array $additionalData + ): OrderInterface { $isAutoCapture = $this->paymentMethodsHelper->isAutoCapture($order, $paymentMethod); $this->adyenOrderPaymentHelper->createAdyenOrderPayment( $order, $isAutoCapture, - $merchantReference, $pspReference, $paymentMethod, $amountValue, @@ -62,18 +63,16 @@ public function execute( $order = $this->orderHelper->setPrePaymentAuthorized($order); $this->orderHelper->updatePaymentDetails($order, $pspReference); - $additionalData = $this->getAdditionalDataArray($notification); $requireFraudManualReview = $this->caseManagementHelper->requiresManualReview($additionalData); $order = $isAutoCapture ? $this->handleAutoCapture( $order, $pspReference, - $merchantReference, $amountValue, $requireFraudManualReview ) - : $this->handleManualCapture($order, $pspReference, $merchantReference, $requireFraudManualReview); + : $this->handleManualCapture($order, $pspReference, $requireFraudManualReview); $this->sendOrderMailIfNeeded($order, $paymentMethod); @@ -85,50 +84,45 @@ public function execute( $order->setData('adyen_notification_payment_captured', 1); } } + return $order; } /** - * @param Order $order + * @param OrderInterface $order * @param string $pspReference - * @param string $merchantReference * @param int $amountValue * @param bool $requireFraudManualReview - * @return Order + * @return OrderInterface * @throws LocalizedException */ private function handleAutoCapture( - Order $order, + OrderInterface $order, string $pspReference, - string $merchantReference, int $amountValue, bool $requireFraudManualReview - ): Order - { - $this->invoiceHelper->createInvoice($order, true, $pspReference, $merchantReference, $amountValue); + ): OrderInterface { + $this->invoiceHelper->createInvoice($order, true, $pspReference, $amountValue); if ($requireFraudManualReview) { $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference, true); } else { - $order = $this->orderHelper->finalizeOrder($order, $pspReference, $merchantReference, $amountValue); + $order = $this->orderHelper->finalizeOrder($order, $pspReference, $amountValue); } return $order; } /** - * @param Order $order + * @param OrderInterface $order * @param string $pspReference - * @param string $merchantReference * @param bool $requireFraudManualReview - * @return Order + * @return OrderInterface */ private function handleManualCapture( - Order $order, + OrderInterface $order, string $pspReference, - string $merchantReference, bool $requireFraudManualReview - ): Order - { + ): OrderInterface { if ($requireFraudManualReview) { $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference); } else { @@ -137,7 +131,7 @@ private function handleManualCapture( 'Capture mode is set to Manual', [ 'pspReference' => $pspReference, - 'merchantReference' => $merchantReference + 'merchantReference' => $order->getIncrementId() ] ); } @@ -145,7 +139,13 @@ private function handleManualCapture( return $order; } - private function sendOrderMailIfNeeded(Order $order, string $paymentMethod): void + /** + * @param OrderInterface $order + * @param string $paymentMethod + * + * @return void + */ + private function sendOrderMailIfNeeded(OrderInterface $order, string $paymentMethod): void { // For Boleto confirmation mail is sent on order creation // Send order confirmation mail after invoice creation so merchant can add invoicePDF to this mail @@ -153,10 +153,4 @@ private function sendOrderMailIfNeeded(Order $order, string $paymentMethod): voi $this->orderHelper->sendOrderMail($order); } } - - private function getAdditionalDataArray(Notification $notification): array - { - $raw = $notification->getAdditionalData(); - return !empty($raw) ? (array) $this->serializer->unserialize($raw) : []; - } } diff --git a/Observer/AuthorizeAfterOrderPlacement.php b/Observer/AuthorizeAfterOrderPlacement.php new file mode 100644 index 0000000000..6c9cbb9e38 --- /dev/null +++ b/Observer/AuthorizeAfterOrderPlacement.php @@ -0,0 +1,88 @@ + + */ + +namespace Adyen\Payment\Observer; + +use Adyen\Payment\Api\Data\PaymentResponseInterface; +use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Helper\PaymentResponseHandler; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\AuthorizationHandler; +use Adyen\Payment\Model\ResourceModel\PaymentResponse\Collection as AdyenPaymentResponseCollection; +use Exception; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; + +readonly class AuthorizeAfterOrderPlacement implements ObserverInterface +{ + /** + * @param AuthorizationHandler $authorizationHandler + * @param AdyenPaymentResponseCollection $adyenPaymentResponseCollection + * @param PaymentMethods $paymentMethods + * @param AdyenLogger $adyenLogger + * @param OrderRepositoryInterface $orderRepository + */ + public function __construct( + private AuthorizationHandler $authorizationHandler, + private AdyenPaymentResponseCollection $adyenPaymentResponseCollection, + private PaymentMethods $paymentMethods, + private AdyenLogger $adyenLogger, + private OrderRepositoryInterface $orderRepository + ) {} + + /** + * @param Observer $observer + * @return void + */ + public function execute(Observer $observer): void + { + /** @var Order $order */ + $order = $observer->getData('order'); + + if (!$this->paymentMethods->isAdyenPayment($order->getPayment()->getMethod())) { + return; + } + + try { + $paymentResponses = $this->adyenPaymentResponseCollection + ->getPaymentResponsesWithMerchantReferences([$order->getIncrementId()]); + + foreach ($paymentResponses as $paymentResponse) { + if ($paymentResponse[PaymentResponseInterface::RESULT_CODE] !== PaymentResponseHandler::AUTHORISED) { + continue; + } + + $response = json_decode($paymentResponse['response'], true); + + $order = $this->authorizationHandler->execute( + $order, + $response['paymentMethod']['brand'], + $response['pspReference'], + $response['amount']['value'], + $response['amount']['currency'], + $response['additionalData'] + ); + + $this->orderRepository->save($order); + } + } catch (Exception $e) { + $this->adyenLogger->error( + sprintf( + 'Failed to process authorization after order placement for order #%s: %s', + $order->getIncrementId(), + $e->getMessage() + ) + ); + } + } +} diff --git a/etc/events.xml b/etc/events.xml index 143c4cc829..229ca3d365 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -362,4 +362,7 @@ + + + From 976b303c9f4a113f71f5e2ac28ffbafcd237f4cd Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 11:13:12 +0100 Subject: [PATCH 4/8] [ECP-9878] Make the properties readonly not the class --- .../Webhook/AuthorisationWebhookHandler.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Helper/Webhook/AuthorisationWebhookHandler.php b/Helper/Webhook/AuthorisationWebhookHandler.php index 88caef0fa2..1de8fe1686 100644 --- a/Helper/Webhook/AuthorisationWebhookHandler.php +++ b/Helper/Webhook/AuthorisationWebhookHandler.php @@ -27,7 +27,7 @@ use Magento\Quote\Api\CartRepositoryInterface; use Magento\Sales\Model\Order; -readonly class AuthorisationWebhookHandler implements WebhookHandlerInterface +class AuthorisationWebhookHandler implements WebhookHandlerInterface { /** * @param OrderHelper $orderHelper @@ -41,15 +41,15 @@ * @param AdyenOrderPayment $adyenOrderPaymentResourceModel */ public function __construct( - private OrderHelper $orderHelper, - private AdyenLogger $adyenLogger, - private Config $configHelper, - private CartRepositoryInterface $cartRepository, - private AdyenNotificationRepositoryInterface $notificationRepository, - private CleanupAdditionalInformationInterface $cleanupAdditionalInformation, - private AuthorizationHandler $authorizationHandler, - private SerializerInterface $serializer, - private AdyenOrderPayment $adyenOrderPaymentResourceModel + private readonly OrderHelper $orderHelper, + private readonly AdyenLogger $adyenLogger, + private readonly Config $configHelper, + private readonly CartRepositoryInterface $cartRepository, + private readonly AdyenNotificationRepositoryInterface $notificationRepository, + private readonly CleanupAdditionalInformationInterface $cleanupAdditionalInformation, + private readonly AuthorizationHandler $authorizationHandler, + private readonly SerializerInterface $serializer, + private readonly AdyenOrderPayment $adyenOrderPaymentResourceModel ) { } /** From 3e8d7f6724058fdf7d6a353d93c87569cc075f24 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 11:57:58 +0100 Subject: [PATCH 5/8] [ECP-9878] Make the properties readonly not the class --- Model/AuthorizationHandler.php | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Model/AuthorizationHandler.php b/Model/AuthorizationHandler.php index e5e256460e..074e35aaf5 100644 --- a/Model/AuthorizationHandler.php +++ b/Model/AuthorizationHandler.php @@ -9,9 +9,9 @@ use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; use Magento\Framework\Exception\LocalizedException; -use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; -readonly class AuthorizationHandler +class AuthorizationHandler { /** * @param AdyenOrderPayment $adyenOrderPaymentHelper @@ -22,32 +22,32 @@ * @param AdyenLogger $adyenLogger */ public function __construct( - private AdyenOrderPayment $adyenOrderPaymentHelper, - private CaseManagement $caseManagementHelper, - private Invoice $invoiceHelper, - private PaymentMethods $paymentMethodsHelper, - private OrderHelper $orderHelper, - private AdyenLogger $adyenLogger + private readonly AdyenOrderPayment $adyenOrderPaymentHelper, + private readonly CaseManagement $caseManagementHelper, + private readonly Invoice $invoiceHelper, + private readonly PaymentMethods $paymentMethodsHelper, + private readonly OrderHelper $orderHelper, + private readonly AdyenLogger $adyenLogger ) { } /** - * @param OrderInterface $order + * @param Order $order * @param string $paymentMethod * @param string $pspReference * @param int $amountValue * @param string $amountCurrency * @param array $additionalData - * @return OrderInterface + * @return Order * @throws LocalizedException */ public function execute( - OrderInterface $order, + Order $order, string $paymentMethod, string $pspReference, int $amountValue, string $amountCurrency, array $additionalData - ): OrderInterface { + ): Order { $isAutoCapture = $this->paymentMethodsHelper->isAutoCapture($order, $paymentMethod); $this->adyenOrderPaymentHelper->createAdyenOrderPayment( @@ -89,19 +89,19 @@ public function execute( } /** - * @param OrderInterface $order + * @param Order $order * @param string $pspReference * @param int $amountValue * @param bool $requireFraudManualReview - * @return OrderInterface + * @return Order * @throws LocalizedException */ private function handleAutoCapture( - OrderInterface $order, + Order $order, string $pspReference, int $amountValue, bool $requireFraudManualReview - ): OrderInterface { + ): Order { $this->invoiceHelper->createInvoice($order, true, $pspReference, $amountValue); if ($requireFraudManualReview) { $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference, true); @@ -113,16 +113,16 @@ private function handleAutoCapture( } /** - * @param OrderInterface $order + * @param Order $order * @param string $pspReference * @param bool $requireFraudManualReview - * @return OrderInterface + * @return Order */ private function handleManualCapture( - OrderInterface $order, + Order $order, string $pspReference, bool $requireFraudManualReview - ): OrderInterface { + ): Order { if ($requireFraudManualReview) { $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference); } else { @@ -140,12 +140,12 @@ private function handleManualCapture( } /** - * @param OrderInterface $order + * @param Order $order * @param string $paymentMethod * * @return void */ - private function sendOrderMailIfNeeded(OrderInterface $order, string $paymentMethod): void + private function sendOrderMailIfNeeded(Order $order, string $paymentMethod): void { // For Boleto confirmation mail is sent on order creation // Send order confirmation mail after invoice creation so merchant can add invoicePDF to this mail From fdf66a12f0b2bfdf6c67e7e92c2419f3750edf75 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 11:58:09 +0100 Subject: [PATCH 6/8] [ECP-9878] Fix unit tests --- Test/Unit/Helper/AdyenOrderPaymentTest.php | 19 +- Test/Unit/Helper/InvoiceTest.php | 14 +- Test/Unit/Helper/OrderTest.php | 25 +- .../Helper/PaymentResponseHandlerTest.php | 21 +- .../AuthorisationWebhookHandlerTest.php | 694 +++--------------- .../Webhook/CaptureWebhookHandlerTest.php | 8 +- 6 files changed, 154 insertions(+), 627 deletions(-) diff --git a/Test/Unit/Helper/AdyenOrderPaymentTest.php b/Test/Unit/Helper/AdyenOrderPaymentTest.php index 37c9806f77..2b8488b159 100644 --- a/Test/Unit/Helper/AdyenOrderPaymentTest.php +++ b/Test/Unit/Helper/AdyenOrderPaymentTest.php @@ -39,17 +39,11 @@ public function testCreateAdyenOrderPayment() 'getId' => $paymentId ]); $order = $this->createConfiguredMock(Order::class, [ - 'getPayment' => $payment + 'getPayment' => $payment, + 'getIncrementId' => $merchantReference ]); $adyenOrderPayment = $this->createMock(AdyenPaymentModel::class); - $notification = $this->createConfiguredMock(Notification::class, [ - 'getPspreference' => $pspReference, - 'getMerchantReference' => $merchantReference, - 'getPaymentMethod' => $paymentMethod - ]); - $mockAdyenDataHelper = $this->createGeneratedMock(Data::class, ['originalAmount']); - $mockAdyenOrderPaymentFactory = $this->createGeneratedMock(PaymentFactory::class, ['create']); $adyenOrderPaymentHelper = $this->createAdyenOrderPaymentHelper( @@ -64,7 +58,14 @@ public function testCreateAdyenOrderPayment() $mockAdyenDataHelper->method('originalAmount')->willReturn($amount); $mockAdyenOrderPaymentFactory->method('create')->willReturn($adyenOrderPayment); - $result = $adyenOrderPaymentHelper->createAdyenOrderPayment($order, $notification, true); + $result = $adyenOrderPaymentHelper->createAdyenOrderPayment( + $order, + true, + $pspReference, + $paymentMethod, + $amount, + 'EUR' + ); $this->assertInstanceOf(AdyenPaymentModel::class, $result); } diff --git a/Test/Unit/Helper/InvoiceTest.php b/Test/Unit/Helper/InvoiceTest.php index 7c8997910b..28b53902cc 100644 --- a/Test/Unit/Helper/InvoiceTest.php +++ b/Test/Unit/Helper/InvoiceTest.php @@ -63,12 +63,11 @@ public function testCreateInvoice() $invoiceHelper = $this->createInvoiceHelper($contextMock); - $notificationMock = $this->createWebhook(); - $invoice = $invoiceHelper->createInvoice( $orderMock, - $notificationMock, - true + true, + 'ABCD1234GHJK5678', + 1000 ); $this->assertInstanceOf(InvoiceModel::class, $invoice); @@ -111,8 +110,6 @@ public function testCreateInvoiceManualCapture($notificationAmount = 1000) $invoiceHelper = $this->createInvoiceHelper($contextMock); - $notificationMock = $this->createWebhook(null, null, $notificationAmount); - if ($notificationAmount == 0) { $invoiceMock->expects($this->once()) ->method('setRequestedCaptureCase') @@ -125,8 +122,9 @@ public function testCreateInvoiceManualCapture($notificationAmount = 1000) $invoice = $invoiceHelper->createInvoice( $orderMock, - $notificationMock, - false + false, + 'ABCD1234GHJK5678', + $notificationAmount ); $this->assertInstanceOf(InvoiceModel::class, $invoice); diff --git a/Test/Unit/Helper/OrderTest.php b/Test/Unit/Helper/OrderTest.php index 514501ecc5..084c7537f9 100644 --- a/Test/Unit/Helper/OrderTest.php +++ b/Test/Unit/Helper/OrderTest.php @@ -37,7 +37,6 @@ use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\Order\Payment\Transaction\Builder; use Magento\Sales\Model\Order\Shipment; -use Magento\Sales\Model\Order\Status\HistoryFactory; use Magento\Sales\Model\OrderRepository; use Magento\Sales\Model\ResourceModel\Order\Status\CollectionFactory as OrderStatusCollectionFactory; use Magento\Sales\Api\Data\TransactionInterface; @@ -63,10 +62,9 @@ public function testFinalizeOrderFinalized() ); $order = $this->createOrder('testStatus'); - $notification = $this->createWebhook(); $order->expects($this->once())->method('setState')->with(MagentoOrder::STATE_PROCESSING); - $orderHelper->finalizeOrder($order, $notification); + $orderHelper->finalizeOrder($order, 'ABCD1234GHJK5678', 1000); } public function testFinalizeOrderPartialPayment() @@ -91,10 +89,9 @@ public function testFinalizeOrderPartialPayment() ); $order = $this->createOrder('testStatus'); - $notification = $this->createWebhook(); $order->expects($this->never())->method('setState')->with(MagentoOrder::STATE_PROCESSING); - $orderHelper->finalizeOrder($order, $notification); + $orderHelper->finalizeOrder($order, 'ABCD1234GHJK5678', 1000); } public function testHoldCancelOrderCancel() @@ -337,7 +334,7 @@ public function testUpdatePaymentDetailsWithOrderInitiallyInStatePaymentReview() $transactionBuilderMock ); - $result = $orderHelper->updatePaymentDetails($orderMock, $notificationMock); + $result = $orderHelper->updatePaymentDetails($orderMock, $pspReference); $this->assertInstanceOf(Transaction::class, $result); } @@ -356,10 +353,6 @@ public function testUpdatePaymentDetailsWithOrderNotInStatePaymentReview() 'setState' => MagentoOrder::STATE_NEW ]); - $notificationMock = $this->createConfiguredMock(Notification::class, [ - 'getPspReference' => $pspReference - ]); - $transactionBuilderMock = $this->createMock(Builder::class); $transactionMock = $this->createMock(Transaction::class); $transactionBuilderMock->expects($this->once()) @@ -399,7 +392,7 @@ public function testUpdatePaymentDetailsWithOrderNotInStatePaymentReview() $transactionBuilderMock ); - $result = $orderHelper->updatePaymentDetails($orderMock, $notificationMock); + $result = $orderHelper->updatePaymentDetails($orderMock, $pspReference); $this->assertEquals($transactionMock, $result); } @@ -782,8 +775,7 @@ protected function createOrderHelper( $adyenCreditmemoHelper = null, $statusResolver = null, $adyenCreditmemoRepositoryMock = null, - $orderManagermentMock = null, - $orderHistoryFactoryMock = null + $orderManagermentMock = null ): Order { $context = $this->createMock(Context::class); @@ -860,10 +852,6 @@ protected function createOrderHelper( $orderManagermentMock = $this->createMock(OrderService::class); } - if (is_null($orderHistoryFactoryMock)) { - $orderHistoryFactoryMock = $this->createMock(HistoryFactory::class); - } - return new Order( $context, $builder, @@ -883,8 +871,7 @@ protected function createOrderHelper( $adyenCreditmemoHelper, $statusResolver, $adyenCreditmemoRepositoryMock, - $orderManagermentMock, - $orderHistoryFactoryMock + $orderManagermentMock ); } } diff --git a/Test/Unit/Helper/PaymentResponseHandlerTest.php b/Test/Unit/Helper/PaymentResponseHandlerTest.php index 5579bb48ba..cc6d7883a0 100644 --- a/Test/Unit/Helper/PaymentResponseHandlerTest.php +++ b/Test/Unit/Helper/PaymentResponseHandlerTest.php @@ -18,6 +18,7 @@ use Adyen\Payment\Helper\Vault; use Adyen\Payment\Helper\Quote; use Adyen\Payment\Helper\Order as OrderHelper; +use Adyen\Payment\Model\AuthorizationHandler; use Adyen\Payment\Model\Method\Adapter; use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; use Exception; @@ -50,6 +51,7 @@ class PaymentResponseHandlerTest extends AbstractAdyenTestCase private Adapter|MockObject $paymentMethodInstanceMock; private PaymentMethods|MockObject $paymentMethodsHelperMock; private OrdersApi|MockObject $ordersApiHelperMock; + private AuthorizationHandler|MockObject $authorizationHandlerMock; protected function setUp(): void { @@ -63,6 +65,7 @@ protected function setUp(): void $this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); $orderStatusHistoryMock = $this->createMock(OrderStatusHistory::class); $this->ordersApiHelperMock = $this->createMock(OrdersApi::class); + $this->authorizationHandlerMock = $this->createMock(AuthorizationHandler::class); // Other functional mocks $this->paymentMock = $this->createMock(Payment::class); @@ -92,7 +95,8 @@ protected function setUp(): void $this->stateDataHelperMock, $this->paymentMethodsHelperMock, $orderStatusHistoryMock, - $this->ordersApiHelperMock + $this->ordersApiHelperMock, + $this->authorizationHandlerMock ); } @@ -246,7 +250,7 @@ public function testHandlePaymentsDetailsResponseWithNullResultCode() public function testHandlePaymentsDetailsResponseAuthorised() { $ccType = 'visa'; - + $paymentsDetailsResponse = [ 'resultCode' => PaymentResponseHandler::AUTHORISED, 'pspReference' => 'ABC123456789', @@ -261,7 +265,11 @@ public function testHandlePaymentsDetailsResponseAuthorised() 'someData' => 'someValue' ], 'donationToken' => 'XYZ123456789', - 'merchantReference' => self::MERCHANT_REFERENCE + 'merchantReference' => self::MERCHANT_REFERENCE, + 'amount' => [ + 'value' => 1000, + 'currency' => 'EUR' + ] ]; // Mock that cc_type is initially null @@ -687,10 +695,15 @@ public function testHandlePaymentsDetailsResponseSetsCcType() // Payment details response with a payment method brand $paymentsDetailsResponse = [ 'resultCode' => PaymentResponseHandler::AUTHORISED, + 'pspReference' => 'ABC123456789', 'paymentMethod' => [ 'brand' => 'VI' ], - 'merchantReference' => self::MERCHANT_REFERENCE + 'merchantReference' => self::MERCHANT_REFERENCE, + 'amount' => [ + 'value' => 1000, + 'currency' => 'EUR' + ] ]; // Expect the `setCcType` method to be called on the payment object with the correct value diff --git a/Test/Unit/Helper/Webhook/AuthorisationWebhookHandlerTest.php b/Test/Unit/Helper/Webhook/AuthorisationWebhookHandlerTest.php index 4f367429e6..d2f5340f1a 100644 --- a/Test/Unit/Helper/Webhook/AuthorisationWebhookHandlerTest.php +++ b/Test/Unit/Helper/Webhook/AuthorisationWebhookHandlerTest.php @@ -4,11 +4,10 @@ use Adyen\Payment\Api\CleanupAdditionalInformationInterface; use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface; -use Adyen\Payment\Helper\AdyenOrderPayment; -use Adyen\Payment\Model\AdyenAmountCurrency; +use Adyen\Payment\Model\AuthorizationHandler; use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; -use Adyen\Payment\Model\Ui\AdyenCcConfigProviderTest; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; +use Adyen\Payment\Model\ResourceModel\Order\Payment as AdyenOrderPaymentResourceModel; use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; use Adyen\Payment\Helper\Webhook\AuthorisationWebhookHandler; use Adyen\Payment\Model\Notification; @@ -19,11 +18,7 @@ use Magento\Sales\Model\Order; use PHPUnit\Framework\MockObject\MockObject; use Adyen\Payment\Helper\Order as OrderHelper; -use Adyen\Payment\Helper\CaseManagement; -use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Config; -use Adyen\Payment\Helper\Invoice; -use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; use Magento\Framework\Serialize\SerializerInterface; use ReflectionClass; @@ -35,19 +30,31 @@ class AuthorisationWebhookHandlerTest extends AbstractAdyenTestCase private Notification|MockObject $notificationMock; private Order|MockObject $orderMock; private Quote|MockObject $quoteMock; - private AdyenOrderPayment|MockObject $adyenOrderPaymentMock; private OrderHelper|MockObject $orderHelperMock; - private CaseManagement|MockObject $caseManagementMock; + private AdyenLogger|MockObject $adyenLoggerMock; + private Config|MockObject $configHelperMock; + private CartRepositoryInterface|MockObject $cartRepositoryMock; + private AdyenNotificationRepositoryInterface|MockObject $notificationRepositoryMock; + private CleanupAdditionalInformationInterface|MockObject $cleanupAdditionalInformationMock; + private AuthorizationHandler|MockObject $authorizationHandlerMock; + private SerializerInterface|MockObject $serializerMock; + private AdyenOrderPaymentResourceModel|MockObject $adyenOrderPaymentResourceModelMock; + private AuthorisationWebhookHandler $authorisationWebhookHandler; protected function setUp(): void { parent::setUp(); - $this->orderMock = $this->createOrder(); - $this->adyenOrderPaymentMock = $this->createMock(AdyenOrderPayment::class); $this->orderMock = $this->createMock(Order::class); $this->orderHelperMock = $this->createMock(OrderHelper::class); - $this->caseManagementMock = $this->createMock(CaseManagement::class); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + $this->configHelperMock = $this->createMock(Config::class); + $this->cartRepositoryMock = $this->createMock(CartRepositoryInterface::class); + $this->notificationRepositoryMock = $this->createMock(AdyenNotificationRepositoryInterface::class); + $this->cleanupAdditionalInformationMock = $this->createMock(CleanupAdditionalInformationInterface::class); + $this->authorizationHandlerMock = $this->createMock(AuthorizationHandler::class); + $this->serializerMock = $this->createMock(SerializerInterface::class); + $this->adyenOrderPaymentResourceModelMock = $this->createMock(AdyenOrderPaymentResourceModel::class); $paymentMethod = 'ADYEN_CC'; $merchantReference = 'TestMerchant'; @@ -57,140 +64,91 @@ protected function setUp(): void 'getPspreference' => $pspReference, 'getMerchantReference' => $merchantReference, 'getPaymentMethod' => $paymentMethod, + 'getAmountValue' => 1000, + 'getAmountCurrency' => 'EUR', + 'getAdditionalData' => null, 'isSuccessful' => true ]); + $this->quoteMock = $this->createMock(Quote::class); + + $this->authorisationWebhookHandler = new AuthorisationWebhookHandler( + $this->orderHelperMock, + $this->adyenLoggerMock, + $this->configHelperMock, + $this->cartRepositoryMock, + $this->notificationRepositoryMock, + $this->cleanupAdditionalInformationMock, + $this->authorizationHandlerMock, + $this->serializerMock, + $this->adyenOrderPaymentResourceModelMock + ); } /** - * @throws LocalizedException + * When no order payment exists yet, AuthorizationHandler::execute is called as fallback. + * @throws ReflectionExceptionAlias */ - public function testHandleWebhook(): void + public function testHandleSuccessfulAuthorisationCallsAuthorizationHandler(): void { - // Set up expectations for mock objects - $storeId = 1; $paymentMock = $this->createConfiguredMock(Order\Payment::class, [ - 'getMethod' => 'adyen_cc' + 'getEntityId' => 1 ]); - $this->orderMock->method('getStoreId')->willReturn($storeId); - $this->orderMock->method('getQuoteId')->willReturn('123'); - $this->quoteMock->method('getIsActive')->willReturn(false); $this->orderMock->method('getPayment')->willReturn($paymentMock); + $this->orderMock->method('getQuoteId')->willReturn('123'); - $this->notificationMock->expects($this->once()) - ->method('isSuccessful') - ->willReturn(true); - - $paymentMethodsHelperMock = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true - ]); + $this->adyenOrderPaymentResourceModelMock->method('getOrderPaymentDetails')->willReturn([]); + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->willReturn($this->orderMock); - $transitionState = PaymentStates::STATE_PAID; + $this->orderHelperMock->expects($this->once()) + ->method('addWebhookStatusHistoryComment') + ->with($this->orderMock, $this->notificationMock); - $cartRepositoryMock = $this->createMock(CartRepositoryInterface::class); - $cartRepositoryMock->expects($this->once())->method('get')->willReturn($this->quoteMock); + $this->cartRepositoryMock->expects($this->once()) + ->method('get') + ->with('123') + ->willReturn($this->quoteMock); - $handler = $this->createAuthorisationWebhookHandler( - null, - $this->orderHelperMock, - null, - null, - null, - null, - null, - $paymentMethodsHelperMock, - $cartRepositoryMock + $method = $this->getPrivateMethod( + AuthorisationWebhookHandler::class, + 'handleSuccessfulAuthorisation' ); - // Call the function to be tested - $result = $handler->handleWebhook($this->orderMock, $this->notificationMock, $transitionState); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); - // Assertions $this->assertInstanceOf(Order::class, $result); } - public function isAutoCaptureProvider(): array - { - return [[true], [false]]; - } - /** - * @dataProvider isAutoCaptureProvider + * When order payment already exists, AuthorizationHandler::execute is NOT called. + * @throws ReflectionExceptionAlias */ - public function testHandleSuccessfulAuthorisation($isAutoCapture): void + public function testHandleSuccessfulAuthorisationSkipsAuthorizationHandlerWhenPaymentExists(): void { - // Mock - $orderAmount = 10.33; - $this->adyenOrderPaymentMock->expects($this->once()) - ->method('createAdyenOrderPayment'); - $this->adyenOrderPaymentMock->expects($this->once()) - ->method('isFullAmountAuthorized') - ->willReturn(true); - - $orderAmountCurrency = new AdyenAmountCurrency( - $orderAmount, - 'EUR', - null, - null, - $orderAmount - ); - - $mockChargedCurrency = $this->createConfiguredMock(ChargedCurrency::class, [ - 'getOrderAmountCurrency' => $orderAmountCurrency + $paymentMock = $this->createConfiguredMock(Order\Payment::class, [ + 'getEntityId' => 1 ]); - - // Create mock instances for Order and Notification - $paymentMock = $this->createMock(Order\Payment::class); - $storeId = 1; - $this->orderMock->method('getStoreId')->willReturn($storeId); - $this->orderMock->method('getQuoteId')->willReturn('123'); $this->orderMock->method('getPayment')->willReturn($paymentMock); - $this->quoteMock->method('getIsActive')->willReturn(false); - - $this->orderHelperMock->expects($this->once()) - ->method('setPrePaymentAuthorized')->willReturn($this->orderMock); - $this->orderHelperMock->expects($this->once()) - ->method('updatePaymentDetails'); - $this->orderHelperMock->expects($this->once()) - ->method('sendOrderMail'); - $this->orderHelperMock->expects($this->once()) - ->method('finalizeOrder')->willReturn($this->orderMock); + $this->orderMock->method('getQuoteId')->willReturn(null); - $paymentMethodsMock = $this->createConfiguredMock( - PaymentMethods::class, - [ - 'isAutoCapture' => true - ] - ); + $this->adyenOrderPaymentResourceModelMock->method('getOrderPaymentDetails') + ->willReturn([['entity_id' => 10]]); - $cartRepositoryMock = $this->createMock(CartRepositoryInterface::class); - $cartRepositoryMock->expects($this->once())->method('get')->willReturn($this->quoteMock); + $this->authorizationHandlerMock->expects($this->never())->method('execute'); - $authorisationWebhookHandler = $this->createAuthorisationWebhookHandler( - $this->adyenOrderPaymentMock, - $this->orderHelperMock, - null, - null, - null, - null, - null, - $paymentMethodsMock, - $cartRepositoryMock - ); + $this->orderHelperMock->expects($this->once()) + ->method('addWebhookStatusHistoryComment'); - // Invoke the private method - $handleSuccessfulAuthorisationMethod = $this->getPrivateMethod( + $method = $this->getPrivateMethod( AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation' ); - $result = $handleSuccessfulAuthorisationMethod->invokeArgs( - $authorisationWebhookHandler, - [$this->orderMock, $this->notificationMock] - ); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); - // Assert the result $this->assertInstanceOf(Order::class, $result); } @@ -206,9 +164,6 @@ public function testHandleFailedAuthorisationAlreadyProcessed(): void ['adyen_notification_payment_captured', null, false] ]); - // Create an instance of AuthorisationWebhookHandler - $webhookHandler = $this->createAuthorisationWebhookHandler(); - $handleFailedAuthorisationMethod = $this->getPrivateMethod( AuthorisationWebhookHandler::class, 'handleFailedAuthorisation' @@ -216,7 +171,7 @@ public function testHandleFailedAuthorisationAlreadyProcessed(): void // Call the private method directly and provide required parameters $result = $handleFailedAuthorisationMethod->invokeArgs( - $webhookHandler, + $this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock] ); @@ -241,9 +196,6 @@ public function testHandleFailedAuthorisation(): void ['adyen_notification_payment_captured', null, false] ]); - // Create an instance of AuthorisationWebhookHandler - $webhookHandler = $this->createAuthorisationWebhookHandler(); - $handleFailedAuthorisationMethod = $this->getPrivateMethod( AuthorisationWebhookHandler::class, 'handleFailedAuthorisation' @@ -251,7 +203,7 @@ public function testHandleFailedAuthorisation(): void // Call the private method directly and provide required parameters $result = $handleFailedAuthorisationMethod->invokeArgs( - $webhookHandler, + $this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock] ); @@ -259,66 +211,25 @@ public function testHandleFailedAuthorisation(): void $this->assertInstanceOf(Order::class, $result); } - /** - * @throws ReflectionExceptionAlias - */ - public function testHandleAutoCapture(): void - { - // Set up expectations for the mocks - $this->orderMock->expects($this->any()) - ->method('getPayment') - ->willReturn($this->orderMock); - - $this->orderMock->expects($this->any()) - ->method('getConfig') - ->willReturnSelf(); - - // Create an instance of AuthorisationWebhookHandler - $webhookHandler = $this->createAuthorisationWebhookHandler(); - - // Use Reflection to access the private method - $handleAutoCaptureMethod = $this->getPrivateMethod( - AuthorisationWebhookHandler::class, - 'handleAutoCapture' - ); - - // Call the private method directly and provide required parameters - $result = $handleAutoCaptureMethod->invokeArgs( - $webhookHandler, - [$this->orderMock, $this->notificationMock, true] // true indicates requireFraudManualReview - ); - - // Perform assertions on the result and expected behavior - $this->assertInstanceOf(Order::class, $result); - } - public function testDisableQuote(): void { - $this->orderMock->expects($this->any())->method('getPayment')->willReturn($this->orderMock); - $this->orderMock->expects($this->any())->method('getConfig')->willReturnSelf(); - + $paymentMock = $this->createConfiguredMock(Order\Payment::class, [ + 'getEntityId' => 1 + ]); + $this->orderMock->method('getPayment')->willReturn($paymentMock); $this->orderMock->method('getQuoteId')->willReturn('123'); $this->quoteMock->expects($this->any())->method('getIsActive')->willReturn(true); $this->quoteMock->expects($this->any())->method('setIsActive')->with(false); - $cartRepositoryMock = $this->createMock(CartRepositoryInterface::class); - $cartRepositoryMock->expects($this->once())->method('get')->with('123')->willReturn($this->quoteMock); - $cartRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); - - $webhookHandler = $this->createAuthorisationWebhookHandler( - null, - null, - null, - null, - null, - null, - null, - null, - $cartRepositoryMock - ); + $this->cartRepositoryMock->expects($this->once())->method('get')->with('123')->willReturn($this->quoteMock); + $this->cartRepositoryMock->expects($this->once())->method('save')->with($this->quoteMock); + + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->willReturn($this->orderMock); - $result = $webhookHandler->handleWebhook( + $result = $this->authorisationWebhookHandler->handleWebhook( $this->orderMock, $this->notificationMock, PaymentStates::STATE_PAID @@ -327,42 +238,6 @@ public function testDisableQuote(): void $this->assertInstanceOf(Order::class, $result); } - /** - * @throws ReflectionExceptionAlias - */ - public function testHandleManualCapture(): void - { - // Set up expectations for handleManualCapture private method - $this->orderHelperMock->expects($this->never()) // Since the condition is true - ->method('setPrePaymentAuthorized'); - - $this->caseManagementMock->expects($this->once()) - ->method('markCaseAsPendingReview') - ->with($this->orderMock, $this->notificationMock->getPspreference(), false); - - // Create an instance of AuthorisationWebhookHandler - $webhookHandler = $this->createAuthorisationWebhookHandler( - null, - $this->orderHelperMock, - $this->caseManagementMock - ); - - // Use Reflection to access the private method - $handleManualCaptureMethod = $this->getPrivateMethod( - AuthorisationWebhookHandler::class, - 'handleManualCapture' - ); - - // Call the private method directly and provide required parameters - $result = $handleManualCaptureMethod->invokeArgs( - $webhookHandler, - [$this->orderMock, $this->notificationMock, true] - ); - - // Perform assertions on the result and expected behavior - $this->assertInstanceOf(Order::class, $result); - } - /** * @throws ReflectionExceptionAlias */ @@ -389,9 +264,6 @@ public function testCanCancelPayByLinkOrder(): void ->method('setAdditionalInformation') ->with('payByLinkFailureCount', $payByLinkFailureCount + 1); - // Create an instance of AuthorisationWebhookHandler - $webhookHandler = $this->createAuthorisationWebhookHandler(); - // Use Reflection to access the private method $canCancelPayByLinkOrderMethod = $this->getPrivateMethod( AuthorisationWebhookHandler::class, @@ -400,7 +272,7 @@ public function testCanCancelPayByLinkOrder(): void // Call the private method directly and provide required parameters $result = $canCancelPayByLinkOrderMethod->invokeArgs( - $webhookHandler, + $this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock] ); @@ -409,10 +281,6 @@ public function testCanCancelPayByLinkOrder(): void } public function testHandleWebhookFailedRoutesToFailedHandler(): void { - $orderHelper = $this->createMock(OrderHelper::class); - $cleanup = $this->createMock(CleanupAdditionalInformationInterface::class); - $config = $this->createMock(Config::class); - // Important: prevent null->getMethod() $paymentMock = $this->createConfiguredMock(Order\Payment::class, [ 'getMethod' => AdyenCcConfigProvider::CODE // non-PBL path @@ -430,30 +298,16 @@ public function testHandleWebhookFailedRoutesToFailedHandler(): void // Ensure we don't hit the "move from PAYMENT_REVIEW to NEW" path unexpectedly $this->orderMock->method('canCancel')->willReturn(true); - $config->method('getNotificationsCanCancel')->willReturn(false); + $this->configHelperMock->method('getNotificationsCanCancel')->willReturn(false); - $cleanup->expects($this->once())->method('execute')->with($paymentMock); + $this->cleanupAdditionalInformationMock->expects($this->once())->method('execute')->with($paymentMock); - $orderHelper->expects($this->once()) + $this->orderHelperMock->expects($this->once()) ->method('holdCancelOrder') ->with($this->orderMock, true) ->willReturn($this->orderMock); - $handler = $this->createAuthorisationWebhookHandler( - null, - $orderHelper, - null, - null, - null, - $config, - null, - null, - null, - null, - $cleanup - ); - - $result = $handler->handleWebhook( + $result = $this->authorisationWebhookHandler->handleWebhook( $this->orderMock, $this->notificationMock, PaymentStates::STATE_FAILED @@ -462,220 +316,31 @@ public function testHandleWebhookFailedRoutesToFailedHandler(): void $this->assertInstanceOf(Order::class, $result); } - /** - * Full amount NOT authorized -> just adds webhook history comment (no finalize/invoice/cleanup) - * @throws ReflectionExceptionAlias - */ - public function testHandleSuccessfulAuthorisationNotFullAmountAuthorizedAddsHistoryCommentOnly(): void - { - $adyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - $adyenOrderPayment->expects($this->once())->method('createAdyenOrderPayment'); - $adyenOrderPayment->expects($this->once()) - ->method('isFullAmountAuthorized') - ->willReturn(false); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->expects($this->once()) - ->method('addWebhookStatusHistoryComment') - ->with($this->orderMock, $this->notificationMock) - ->willReturn($this->orderMock); - - $orderHelper->expects($this->never())->method('finalizeOrder'); - $orderHelper->expects($this->never())->method('sendOrderMail'); - - $paymentMethods = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true - ]); - - $handler = $this->createAuthorisationWebhookHandler( - $adyenOrderPayment, - $orderHelper, - null, - null, - null, - null, - null, - $paymentMethods - ); - - $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); - - $this->assertInstanceOf(Order::class, $result); - } - - /** - * Boleto -> order mail should NOT be sent. - * @throws ReflectionExceptionAlias - */ - public function testHandleSuccessfulAuthorisationBoletoDoesNotSendOrderMail(): void - { - $notification = $this->createConfiguredMock(Notification::class, [ - 'getPaymentMethod' => 'adyen_boleto', - 'isSuccessful' => true, - 'getAdditionalData' => null - ]); - - $adyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - $adyenOrderPayment->method('isFullAmountAuthorized')->willReturn(true); - - $paymentMethods = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true - ]); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->method('setPrePaymentAuthorized')->willReturn($this->orderMock); - $orderHelper->expects($this->never())->method('sendOrderMail'); - $orderHelper->method('finalizeOrder')->willReturn($this->orderMock); - - $invoiceHelper = $this->createMock(Invoice::class); - $invoiceHelper->expects($this->once())->method('createInvoice'); - - $cleanup = $this->createMock(CleanupAdditionalInformationInterface::class); - $cleanup->expects($this->once())->method('execute'); - - $payment = $this->createMock(Order\Payment::class); - $payment->expects($this->once())->method('setAmountAuthorized'); - $payment->expects($this->once())->method('setBaseAmountAuthorized'); - - $this->orderMock->method('getPayment')->willReturn($payment); - $this->orderMock->method('getEmailSent')->willReturn(false); - $this->orderMock->method('getGrandTotal')->willReturn(10.0); - $this->orderMock->method('getBaseGrandTotal')->willReturn(10.0); - $this->orderMock->method('getQuoteId')->willReturn(null); - - $handler = $this->createAuthorisationWebhookHandler( - $adyenOrderPayment, - $orderHelper, - $this->createMock(CaseManagement::class), - $this->createMock(SerializerInterface::class), - $this->createMock(AdyenLogger::class), - $this->createMock(Config::class), - $invoiceHelper, - $paymentMethods, - $this->createMock(CartRepositoryInterface::class), - null, - $cleanup - ); - - $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $notification]); - - $this->assertInstanceOf(Order::class, $result); - } - - /** - * c_cash + create_shipment enabled -> createShipment called - * @throws ReflectionExceptionAlias - */ - public function testHandleSuccessfulAuthorisationCashCreatesShipmentWhenConfigured(): void - { - $notification = $this->createConfiguredMock(Notification::class, [ - 'getPaymentMethod' => 'c_cash', - 'isSuccessful' => true, - 'getAdditionalData' => null - ]); - - $adyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - $adyenOrderPayment->method('isFullAmountAuthorized')->willReturn(true); - - $paymentMethods = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true - ]); - - $config = $this->createMock(Config::class); - $config->expects($this->once()) - ->method('getConfigData') - ->with('create_shipment', 'adyen_cash', $this->anything()) - ->willReturn(true); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->method('setPrePaymentAuthorized')->willReturn($this->orderMock); - $orderHelper->method('finalizeOrder')->willReturn($this->orderMock); - $orderHelper->expects($this->once())->method('createShipment')->with($this->orderMock); - - $payment = $this->createMock(Order\Payment::class); - $payment->expects($this->once())->method('setAmountAuthorized'); - $payment->expects($this->once())->method('setBaseAmountAuthorized'); - - $this->orderMock->method('getPayment')->willReturn($payment); - $this->orderMock->method('getStoreId')->willReturn(1); - $this->orderMock->method('getEmailSent')->willReturn(true); - $this->orderMock->method('getGrandTotal')->willReturn(10.0); - $this->orderMock->method('getBaseGrandTotal')->willReturn(10.0); - $this->orderMock->method('getQuoteId')->willReturn(null); - - $handler = $this->createAuthorisationWebhookHandler( - $adyenOrderPayment, - $orderHelper, - $this->createMock(CaseManagement::class), - $this->createMock(SerializerInterface::class), - $this->createMock(AdyenLogger::class), - $config, - $this->createMock(Invoice::class), - $paymentMethods, - $this->createMock(CartRepositoryInterface::class), - null, - $this->createMock(CleanupAdditionalInformationInterface::class) - ); - - $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $notification]); - - $this->assertInstanceOf(Order::class, $result); - } - /** * Quote is active -> setIsActive(false) and save. * @throws ReflectionExceptionAlias */ public function testHandleSuccessfulAuthorisationDeactivatesActiveQuote(): void { - $adyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - $adyenOrderPayment->method('isFullAmountAuthorized')->willReturn(true); - - $paymentMethods = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true + $payment = $this->createConfiguredMock(Order\Payment::class, [ + 'getEntityId' => 1 ]); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->method('setPrePaymentAuthorized')->willReturn($this->orderMock); - $orderHelper->method('finalizeOrder')->willReturn($this->orderMock); - - $payment = $this->createMock(Order\Payment::class); - $payment->expects($this->once())->method('setAmountAuthorized'); - $payment->expects($this->once())->method('setBaseAmountAuthorized'); - $this->orderMock->method('getPayment')->willReturn($payment); - $this->orderMock->method('getEmailSent')->willReturn(true); - $this->orderMock->method('getGrandTotal')->willReturn(10.0); - $this->orderMock->method('getBaseGrandTotal')->willReturn(10.0); $this->orderMock->method('getQuoteId')->willReturn(123); $quote = $this->createMock(Quote::class); $quote->method('getIsActive')->willReturn(true); $quote->expects($this->once())->method('setIsActive')->with(false); - $cartRepo = $this->createMock(CartRepositoryInterface::class); - $cartRepo->expects($this->once())->method('get')->with(123)->willReturn($quote); - $cartRepo->expects($this->once())->method('save')->with($quote); - - $handler = $this->createAuthorisationWebhookHandler( - $adyenOrderPayment, - $orderHelper, - $this->createMock(CaseManagement::class), - $this->createMock(SerializerInterface::class), - $this->createMock(AdyenLogger::class), - $this->createMock(Config::class), - $this->createMock(Invoice::class), - $paymentMethods, - $cartRepo, - null, - $this->createMock(CleanupAdditionalInformationInterface::class) - ); + $this->cartRepositoryMock->expects($this->once())->method('get')->with(123)->willReturn($quote); + $this->cartRepositoryMock->expects($this->once())->method('save')->with($quote); + + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->willReturn($this->orderMock); $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); $this->assertInstanceOf(Order::class, $result); } @@ -686,56 +351,29 @@ public function testHandleSuccessfulAuthorisationDeactivatesActiveQuote(): void */ public function testHandleSuccessfulAuthorisationQuoteDeactivationExceptionLogs(): void { - $adyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - $adyenOrderPayment->method('isFullAmountAuthorized')->willReturn(true); - - $paymentMethods = $this->createConfiguredMock(PaymentMethods::class, [ - 'isAutoCapture' => true + $payment = $this->createConfiguredMock(Order\Payment::class, [ + 'getEntityId' => 1 ]); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->method('setPrePaymentAuthorized')->willReturn($this->orderMock); - $orderHelper->method('finalizeOrder')->willReturn($this->orderMock); - - $payment = $this->createMock(Order\Payment::class); - $payment->expects($this->once())->method('setAmountAuthorized'); - $payment->expects($this->once())->method('setBaseAmountAuthorized'); - $this->orderMock->method('getPayment')->willReturn($payment); - $this->orderMock->method('getEmailSent')->willReturn(true); - $this->orderMock->method('getGrandTotal')->willReturn(10.0); - $this->orderMock->method('getBaseGrandTotal')->willReturn(10.0); $this->orderMock->method('getQuoteId')->willReturn(123); - $cartRepo = $this->createMock(CartRepositoryInterface::class); - $cartRepo->expects($this->once()) + $this->cartRepositoryMock->expects($this->once()) ->method('get') ->willThrowException(new \Exception('boom')); - $logger = $this->createMock(AdyenLogger::class); - $logger->expects($this->once()) + $this->adyenLoggerMock->expects($this->once()) ->method('addAdyenNotification') ->with( $this->stringContains('Quote deactivation skipped'), $this->arrayHasKey('quoteId') ); - $handler = $this->createAuthorisationWebhookHandler( - $adyenOrderPayment, - $orderHelper, - $this->createMock(CaseManagement::class), - $this->createMock(SerializerInterface::class), - $logger, - $this->createMock(Config::class), - $this->createMock(Invoice::class), - $paymentMethods, - $cartRepo, - null, - $this->createMock(CleanupAdditionalInformationInterface::class) - ); + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->willReturn($this->orderMock); $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleSuccessfulAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); $this->assertInstanceOf(Order::class, $result); } @@ -748,13 +386,10 @@ public function testHandleFailedAuthorisationOrderAlreadyCancelledDoesNothing(): { $this->orderMock->method('isCanceled')->willReturn(true); - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->expects($this->never())->method('holdCancelOrder'); - - $handler = $this->createAuthorisationWebhookHandler(null, $orderHelper); + $this->orderHelperMock->expects($this->never())->method('holdCancelOrder'); $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleFailedAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); $this->assertInstanceOf(Order::class, $result); } @@ -779,27 +414,12 @@ public function testHandleFailedAuthorisationPayByLinkBelowMaxDoesNotCancel(): v ['adyen_notification_payment_captured', null, false], ]); - $repo = $this->createMock(AdyenNotificationRepositoryInterface::class); - $repo->expects($this->once())->method('save')->with($this->notificationMock); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->expects($this->never())->method('holdCancelOrder'); - - $handler = $this->createAuthorisationWebhookHandler( - null, - $orderHelper, - null, - null, - $this->createMock(AdyenLogger::class), - $this->createMock(Config::class), - null, - null, - null, - $repo - ); + $this->notificationRepositoryMock->expects($this->once())->method('save')->with($this->notificationMock); + + $this->orderHelperMock->expects($this->never())->method('holdCancelOrder'); $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleFailedAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); $this->assertInstanceOf(Order::class, $result); } @@ -826,28 +446,12 @@ public function testHandleFailedAuthorisationPayByLinkAtMaxCancels(): void ['adyen_notification_payment_captured', null, false], ]); - $cleanup = $this->createMock(CleanupAdditionalInformationInterface::class); - $cleanup->expects($this->once())->method('execute'); - - $orderHelper = $this->createMock(OrderHelper::class); - $orderHelper->expects($this->once())->method('holdCancelOrder')->willReturn($this->orderMock); - - $handler = $this->createAuthorisationWebhookHandler( - null, - $orderHelper, - null, - null, - null, - $this->createMock(Config::class), - null, - null, - null, - $this->createMock(AdyenNotificationRepositoryInterface::class), - $cleanup - ); + $this->cleanupAdditionalInformationMock->expects($this->once())->method('execute'); + + $this->orderHelperMock->expects($this->once())->method('holdCancelOrder')->willReturn($this->orderMock); $method = $this->getPrivateMethod(AuthorisationWebhookHandler::class, 'handleFailedAuthorisation'); - $result = $method->invokeArgs($handler, [$this->orderMock, $this->notificationMock]); + $result = $method->invokeArgs($this->authorisationWebhookHandler, [$this->orderMock, $this->notificationMock]); $this->assertInstanceOf(Order::class, $result); } @@ -862,76 +466,4 @@ private function getPrivateMethod(string $className, string $methodName): Reflec $method->setAccessible(true); return $method; } - - protected function createAuthorisationWebhookHandler( - $mockAdyenOrderPayment = null, - $mockOrderHelper = null, - $mockCaseManagementHelper = null, - $mockSerializer = null, - $mockAdyenLogger = null, - $mockConfigHelper = null, - $mockInvoiceHelper = null, - $mockPaymentMethodsHelper = null, - $mockCartRepositoryMock = null, - $adyenNotificationRepositoryMock = null, - $cleanupAdditionalInformation = null - ): AuthorisationWebhookHandler { - if (is_null($mockAdyenOrderPayment)) { - $mockAdyenOrderPayment = $this->createMock(AdyenOrderPayment::class); - } - - if (is_null($mockOrderHelper)) { - $mockOrderHelper = $this->createMock(OrderHelper::class); - } - - if (is_null($mockCaseManagementHelper)) { - $mockCaseManagementHelper = $this->createMock(CaseManagement::class); - } - - if (is_null($mockSerializer)) { - $mockSerializer = $this->createMock(SerializerInterface::class); - } - - if (is_null($mockAdyenLogger)) { - $mockAdyenLogger = $this->createMock(AdyenLogger::class); - } - - if (is_null($mockConfigHelper)) { - $mockConfigHelper = $this->createMock(Config::class); - } - - if (is_null($mockInvoiceHelper)) { - $mockInvoiceHelper = $this->createMock(Invoice::class); - } - - if (is_null($mockPaymentMethodsHelper)) { - $mockPaymentMethodsHelper = $this->createMock(PaymentMethods::class); - } - - if (is_null($mockCartRepositoryMock)) { - $mockCartRepositoryMock = $this->createMock(CartRepositoryInterface::class); - } - - if (is_null($adyenNotificationRepositoryMock)) { - $adyenNotificationRepositoryMock = $this->createMock(AdyenNotificationRepositoryInterface::class); - } - - if (is_null($cleanupAdditionalInformation)) { - $cleanupAdditionalInformation = $this->createMock(CleanupAdditionalInformationInterface::class); - } - - return new AuthorisationWebhookHandler( - $mockAdyenOrderPayment, - $mockOrderHelper, - $mockCaseManagementHelper, - $mockSerializer, - $mockAdyenLogger, - $mockConfigHelper, - $mockInvoiceHelper, - $mockPaymentMethodsHelper, - $mockCartRepositoryMock, - $adyenNotificationRepositoryMock, - $cleanupAdditionalInformation - ); - } } diff --git a/Test/Unit/Helper/Webhook/CaptureWebhookHandlerTest.php b/Test/Unit/Helper/Webhook/CaptureWebhookHandlerTest.php index 3e7866a899..f2adb4d591 100644 --- a/Test/Unit/Helper/Webhook/CaptureWebhookHandlerTest.php +++ b/Test/Unit/Helper/Webhook/CaptureWebhookHandlerTest.php @@ -30,11 +30,7 @@ protected function setUp(): void // Initialize the CaptureWebhookHandler with mock dependencies. $this->captureWebhookHandler = $this->createCaptureWebhookHandler(); $this->order = $this->createOrder(); - $this->notification = $this->createWebhook(); - $this->notification->method('getEventCode')->willReturn('CAPTURE'); - $this->notification->method('getAmountValue')->willReturn(500); // Partial capture amount - $this->notification->method('getOriginalReference')->willReturn('original_reference'); - $this->notification->method('getPspreference')->willReturn('ABCD1234GHJK5678'); + $this->notification = $this->createWebhook('original_reference', 'ABCD1234GHJK5678', 500); $this->notification->method('getPaymentMethod')->willReturn('ADYEN_CC'); } @@ -133,7 +129,7 @@ public function testHandleWebhookWithoutAutoCapture() ]); $orderHelperMock->expects($this->once()) ->method('finalizeOrder') - ->with($this->order, $this->notification) + ->with($this->isInstanceOf(MagentoOrder::class), 'ABCD1234GHJK5678', 500) ->willReturn($this->order); $adyenOrderPaymentMock = $this->createMock(Payment::class); From ccafb2f03a49d37dce5a397e6c90012da6e6adef Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 11:58:53 +0100 Subject: [PATCH 7/8] [ECP-9878] Enable workflows on develop-11 branch --- .github/workflows/e2e-test.yml | 2 +- .github/workflows/main.yml | 4 ++-- .github/workflows/mftf-test.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 94c008e91d..b0ec632b53 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -3,7 +3,7 @@ run-name: E2E test workflow with test suite branch ${{inputs.testBranch || 'deve on: pull_request: - branches: [main] + branches: [main, develop-11] workflow_dispatch: inputs: testBranch: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1dbfb8a562..00797c9094 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,9 +3,9 @@ run-name: Main on: pull_request: - branches: [main] + branches: [main, develop-11] push: - branches: [main] + branches: [main, develop-11] workflow_dispatch: jobs: diff --git a/.github/workflows/mftf-test.yml b/.github/workflows/mftf-test.yml index 7675900057..d48c0843a9 100644 --- a/.github/workflows/mftf-test.yml +++ b/.github/workflows/mftf-test.yml @@ -2,7 +2,7 @@ name: Functional Tests on: workflow_dispatch: pull_request: - branches: [main] + branches: [main, develop-11] jobs: build-mftf: From 6b8ceb2ca5a3d8dfcb2c9eaee3e6f64cca972315 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Mon, 2 Mar 2026 13:23:12 +0100 Subject: [PATCH 8/8] [ECP-9878] Write more unit tests --- .../ManualReviewAcceptWebhookHandlerTest.php | 137 +++++++ Test/Unit/Model/AuthorizationHandlerTest.php | 371 ++++++++++++++++++ .../AuthorizeAfterOrderPlacementTest.php | 275 +++++++++++++ 3 files changed, 783 insertions(+) create mode 100644 Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php create mode 100644 Test/Unit/Model/AuthorizationHandlerTest.php create mode 100644 Test/Unit/Observer/AuthorizeAfterOrderPlacementTest.php diff --git a/Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php b/Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php new file mode 100644 index 0000000000..3e213bb532 --- /dev/null +++ b/Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php @@ -0,0 +1,137 @@ +order = $this->createOrder(); + $this->notification = $this->createWebhook( + self::ORIGINAL_REFERENCE, + self::PSP_REFERENCE, + self::AMOUNT_VALUE + ); + $this->notification->method('getPaymentMethod')->willReturn(self::PAYMENT_METHOD); + } + + private function createManualReviewAcceptWebhookHandler( + $caseManagementHelper = null, + $paymentMethodsHelper = null, + $orderHelper = null + ): ManualReviewAcceptWebhookHandler { + if ($caseManagementHelper === null) { + $caseManagementHelper = $this->createGeneratedMock(CaseManagement::class, ['markCaseAsAccepted']); + $caseManagementHelper->method('markCaseAsAccepted')->willReturn($this->order); + } + if ($paymentMethodsHelper === null) { + $paymentMethodsHelper = $this->createGeneratedMock(PaymentMethods::class); + } + if ($orderHelper === null) { + $orderHelper = $this->createGeneratedMock(Order::class, ['finalizeOrder']); + } + + return new ManualReviewAcceptWebhookHandler( + $caseManagementHelper, + $paymentMethodsHelper, + $orderHelper + ); + } + + public function testHandleWebhookMarksCaseAsAccepted() + { + $expectedComment = sprintf( + 'Manual review accepted for order w/pspReference: %s', + self::ORIGINAL_REFERENCE + ); + + $caseManagementHelperMock = $this->createGeneratedMock(CaseManagement::class, ['markCaseAsAccepted']); + $caseManagementHelperMock->expects($this->once()) + ->method('markCaseAsAccepted') + ->with($this->order, $expectedComment) + ->willReturn($this->order); + + $paymentMethodsHelperMock = $this->createGeneratedMock(PaymentMethods::class, ['isAutoCapture']); + $paymentMethodsHelperMock->method('isAutoCapture')->willReturn(false); + + $this->manualReviewAcceptWebhookHandler = $this->createManualReviewAcceptWebhookHandler( + $caseManagementHelperMock, + $paymentMethodsHelperMock + ); + + $result = $this->manualReviewAcceptWebhookHandler->handleWebhook($this->order, $this->notification, 'active'); + + $this->assertSame($this->order, $result); + } + + public function testHandleWebhookWithAutoCaptureFinalizesOrder() + { + $caseManagementHelperMock = $this->createGeneratedMock(CaseManagement::class, ['markCaseAsAccepted']); + $caseManagementHelperMock->method('markCaseAsAccepted')->willReturn($this->order); + + $paymentMethodsHelperMock = $this->createGeneratedMock(PaymentMethods::class, ['isAutoCapture']); + $paymentMethodsHelperMock->method('isAutoCapture') + ->with($this->order, self::PAYMENT_METHOD) + ->willReturn(true); + + $orderHelperMock = $this->createGeneratedMock(Order::class, ['finalizeOrder']); + $orderHelperMock->expects($this->once()) + ->method('finalizeOrder') + ->with($this->order, self::PSP_REFERENCE, self::AMOUNT_VALUE) + ->willReturn($this->order); + + $this->manualReviewAcceptWebhookHandler = $this->createManualReviewAcceptWebhookHandler( + $caseManagementHelperMock, + $paymentMethodsHelperMock, + $orderHelperMock + ); + + $result = $this->manualReviewAcceptWebhookHandler->handleWebhook($this->order, $this->notification, 'active'); + + $this->assertSame($this->order, $result); + } + + public function testHandleWebhookWithManualCaptureDoesNotFinalizeOrder() + { + $caseManagementHelperMock = $this->createGeneratedMock(CaseManagement::class, ['markCaseAsAccepted']); + $caseManagementHelperMock->method('markCaseAsAccepted')->willReturn($this->order); + + $paymentMethodsHelperMock = $this->createGeneratedMock(PaymentMethods::class, ['isAutoCapture']); + $paymentMethodsHelperMock->method('isAutoCapture') + ->with($this->order, self::PAYMENT_METHOD) + ->willReturn(false); + + $orderHelperMock = $this->createGeneratedMock(Order::class, ['finalizeOrder']); + $orderHelperMock->expects($this->never())->method('finalizeOrder'); + + $this->manualReviewAcceptWebhookHandler = $this->createManualReviewAcceptWebhookHandler( + $caseManagementHelperMock, + $paymentMethodsHelperMock, + $orderHelperMock + ); + + $result = $this->manualReviewAcceptWebhookHandler->handleWebhook($this->order, $this->notification, 'active'); + + $this->assertSame($this->order, $result); + } +} diff --git a/Test/Unit/Model/AuthorizationHandlerTest.php b/Test/Unit/Model/AuthorizationHandlerTest.php new file mode 100644 index 0000000000..f5b6352ad2 --- /dev/null +++ b/Test/Unit/Model/AuthorizationHandlerTest.php @@ -0,0 +1,371 @@ + + */ + +namespace Adyen\Payment\Test\Unit\Model; + +use Adyen\Payment\Helper\AdyenOrderPayment; +use Adyen\Payment\Helper\CaseManagement; +use Adyen\Payment\Helper\Invoice; +use Adyen\Payment\Helper\Order as OrderHelper; +use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\AuthorizationHandler; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; + +class AuthorizationHandlerTest extends AbstractAdyenTestCase +{ + private AuthorizationHandler $authorizationHandler; + private AdyenOrderPayment|MockObject $adyenOrderPaymentHelperMock; + private CaseManagement|MockObject $caseManagementHelperMock; + private Invoice|MockObject $invoiceHelperMock; + private PaymentMethods|MockObject $paymentMethodsHelperMock; + private OrderHelper|MockObject $orderHelperMock; + private AdyenLogger|MockObject $adyenLoggerMock; + + private const PSP_REFERENCE = 'ABCD1234567890'; + private const AMOUNT_VALUE = 1000; + private const AMOUNT_CURRENCY = 'EUR'; + private const PAYMENT_METHOD = 'adyen_cc'; + private const GRAND_TOTAL = 10.00; + private const BASE_GRAND_TOTAL = 10.00; + + protected function setUp(): void + { + $this->adyenOrderPaymentHelperMock = $this->createMock(AdyenOrderPayment::class); + $this->caseManagementHelperMock = $this->createMock(CaseManagement::class); + $this->invoiceHelperMock = $this->createMock(Invoice::class); + $this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + $this->orderHelperMock = $this->createMock(OrderHelper::class); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + + $this->authorizationHandler = new AuthorizationHandler( + $this->adyenOrderPaymentHelperMock, + $this->caseManagementHelperMock, + $this->invoiceHelperMock, + $this->paymentMethodsHelperMock, + $this->orderHelperMock, + $this->adyenLoggerMock + ); + } + + private function createOrderMock(bool $emailSent = false): Order|MockObject + { + $paymentMock = $this->createMock(Payment::class); + + $orderMock = $this->createMockWithMethods( + Order::class, + [ + 'getPayment', + 'getGrandTotal', + 'getBaseGrandTotal', + 'getEmailSent', + 'getIncrementId', + 'getStatus', + 'addCommentToStatusHistory', + 'setData' + ], + [] + ); + + $orderMock->method('getPayment')->willReturn($paymentMock); + $orderMock->method('getGrandTotal')->willReturn(self::GRAND_TOTAL); + $orderMock->method('getBaseGrandTotal')->willReturn(self::BASE_GRAND_TOTAL); + $orderMock->method('getEmailSent')->willReturn($emailSent); + $orderMock->method('getIncrementId')->willReturn('000000001'); + $orderMock->method('getStatus')->willReturn('processing'); + + return $orderMock; + } + + public function testExecutePartialAuthorization() + { + $orderMock = $this->createOrderMock(); + $additionalData = []; + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(true); + $this->adyenOrderPaymentHelperMock->expects($this->once()) + ->method('createAdyenOrderPayment') + ->with($orderMock, true, self::PSP_REFERENCE, self::PAYMENT_METHOD, self::AMOUNT_VALUE, self::AMOUNT_CURRENCY); + + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(false); + + $this->orderHelperMock->expects($this->never())->method('setPrePaymentAuthorized'); + $this->invoiceHelperMock->expects($this->never())->method('createInvoice'); + + $result = $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ); + + $this->assertSame($orderMock, $result); + } + + public function testExecuteFullAuthorizationAutoCapture() + { + $orderMock = $this->createOrderMock(); + $additionalData = []; + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(true); + + $this->adyenOrderPaymentHelperMock->expects($this->once()) + ->method('createAdyenOrderPayment'); + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + + $this->orderHelperMock->expects($this->once()) + ->method('setPrePaymentAuthorized') + ->with($orderMock) + ->willReturn($orderMock); + $this->orderHelperMock->expects($this->once()) + ->method('updatePaymentDetails') + ->with($orderMock, self::PSP_REFERENCE); + + $this->caseManagementHelperMock->method('requiresManualReview')->with($additionalData)->willReturn(false); + + $this->invoiceHelperMock->expects($this->once()) + ->method('createInvoice') + ->with($orderMock, true, self::PSP_REFERENCE, self::AMOUNT_VALUE); + + $this->orderHelperMock->expects($this->once()) + ->method('finalizeOrder') + ->with($orderMock, self::PSP_REFERENCE, self::AMOUNT_VALUE) + ->willReturn($orderMock); + + $this->orderHelperMock->expects($this->once()) + ->method('sendOrderMail') + ->with($orderMock); + + $orderMock->expects($this->once()) + ->method('setData') + ->with('adyen_notification_payment_captured', 1); + + $result = $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ); + + $this->assertSame($orderMock, $result); + } + + public function testExecuteFullAuthorizationAutoCaptureWithFraudReview() + { + $orderMock = $this->createOrderMock(); + $additionalData = ['fraudManualReview' => 'true']; + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(true); + + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + + $this->caseManagementHelperMock->method('requiresManualReview') + ->with($additionalData) + ->willReturn(true); + + $this->invoiceHelperMock->expects($this->once()) + ->method('createInvoice') + ->with($orderMock, true, self::PSP_REFERENCE, self::AMOUNT_VALUE); + + $this->caseManagementHelperMock->expects($this->once()) + ->method('markCaseAsPendingReview') + ->with($orderMock, self::PSP_REFERENCE, true) + ->willReturn($orderMock); + + $this->orderHelperMock->expects($this->never())->method('finalizeOrder'); + + $result = $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ); + + $this->assertSame($orderMock, $result); + } + + public function testExecuteFullAuthorizationManualCapture() + { + $orderMock = $this->createOrderMock(); + $additionalData = []; + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(false); + + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + + $this->caseManagementHelperMock->method('requiresManualReview')->willReturn(false); + + $this->invoiceHelperMock->expects($this->never())->method('createInvoice'); + + $orderMock->expects($this->once()) + ->method('addCommentToStatusHistory'); + + $this->adyenLoggerMock->expects($this->once()) + ->method('addAdyenNotification') + ->with( + 'Capture mode is set to Manual', + [ + 'pspReference' => self::PSP_REFERENCE, + 'merchantReference' => '000000001' + ] + ); + + $orderMock->expects($this->never())->method('setData'); + + $result = $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ); + + $this->assertSame($orderMock, $result); + } + + public function testExecuteFullAuthorizationManualCaptureWithFraudReview() + { + $orderMock = $this->createOrderMock(); + $additionalData = ['fraudManualReview' => 'true']; + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(false); + + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + + $this->caseManagementHelperMock->method('requiresManualReview')->willReturn(true); + + $this->caseManagementHelperMock->expects($this->once()) + ->method('markCaseAsPendingReview') + ->with($orderMock, self::PSP_REFERENCE) + ->willReturn($orderMock); + + $this->adyenLoggerMock->expects($this->never())->method('addAdyenNotification'); + + $result = $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ); + + $this->assertSame($orderMock, $result); + } + + public function testExecuteBoletoDoesNotSendEmail() + { + $orderMock = $this->createOrderMock(false); + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(true); + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + $this->caseManagementHelperMock->method('requiresManualReview')->willReturn(false); + $this->orderHelperMock->method('finalizeOrder')->willReturn($orderMock); + + $this->orderHelperMock->expects($this->never())->method('sendOrderMail'); + + $this->authorizationHandler->execute( + $orderMock, + 'adyen_boleto', + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + [] + ); + } + + public function testExecuteDoesNotSendEmailIfAlreadySent() + { + $orderMock = $this->createOrderMock(true); + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(true); + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + $this->caseManagementHelperMock->method('requiresManualReview')->willReturn(false); + $this->orderHelperMock->method('finalizeOrder')->willReturn($orderMock); + + $this->orderHelperMock->expects($this->never())->method('sendOrderMail'); + + $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + [] + ); + } + + public function testExecuteSetsAmountAuthorizedOnFullAuthorization() + { + $paymentMock = $this->createMock(Payment::class); + + $orderMock = $this->createMockWithMethods( + Order::class, + [ + 'getPayment', + 'getGrandTotal', + 'getBaseGrandTotal', + 'getEmailSent', + 'getIncrementId', + 'getStatus', + 'addCommentToStatusHistory', + 'setData' + ], + [] + ); + + $orderMock->method('getPayment')->willReturn($paymentMock); + $orderMock->method('getGrandTotal')->willReturn(self::GRAND_TOTAL); + $orderMock->method('getBaseGrandTotal')->willReturn(self::BASE_GRAND_TOTAL); + $orderMock->method('getEmailSent')->willReturn(false); + $orderMock->method('getIncrementId')->willReturn('000000001'); + $orderMock->method('getStatus')->willReturn('processing'); + + $this->paymentMethodsHelperMock->method('isAutoCapture')->willReturn(false); + $this->adyenOrderPaymentHelperMock->method('isFullAmountAuthorized')->willReturn(true); + $this->orderHelperMock->method('setPrePaymentAuthorized')->willReturn($orderMock); + $this->caseManagementHelperMock->method('requiresManualReview')->willReturn(false); + + $paymentMock->expects($this->once()) + ->method('setAmountAuthorized') + ->with(self::GRAND_TOTAL); + $paymentMock->expects($this->once()) + ->method('setBaseAmountAuthorized') + ->with(self::BASE_GRAND_TOTAL); + + $this->authorizationHandler->execute( + $orderMock, + self::PAYMENT_METHOD, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + [] + ); + } +} diff --git a/Test/Unit/Observer/AuthorizeAfterOrderPlacementTest.php b/Test/Unit/Observer/AuthorizeAfterOrderPlacementTest.php new file mode 100644 index 0000000000..3bf5c8ca18 --- /dev/null +++ b/Test/Unit/Observer/AuthorizeAfterOrderPlacementTest.php @@ -0,0 +1,275 @@ + + */ + +namespace Adyen\Payment\Test\Unit\Observer; + +use Adyen\Payment\Api\Data\PaymentResponseInterface; +use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Helper\PaymentResponseHandler; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\AuthorizationHandler; +use Adyen\Payment\Model\ResourceModel\PaymentResponse\Collection as AdyenPaymentResponseCollection; +use Adyen\Payment\Observer\AuthorizeAfterOrderPlacement; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Exception; +use Magento\Framework\Event\Observer; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; + +class AuthorizeAfterOrderPlacementTest extends AbstractAdyenTestCase +{ + private AuthorizeAfterOrderPlacement $authorizeAfterOrderPlacement; + private AuthorizationHandler|MockObject $authorizationHandlerMock; + private AdyenPaymentResponseCollection|MockObject $adyenPaymentResponseCollectionMock; + private PaymentMethods|MockObject $paymentMethodsMock; + private AdyenLogger|MockObject $adyenLoggerMock; + private OrderRepositoryInterface|MockObject $orderRepositoryMock; + private Observer|MockObject $observerMock; + private Order|MockObject $orderMock; + private Payment|MockObject $paymentMock; + + private const INCREMENT_ID = '000000001'; + private const PSP_REFERENCE = 'ABCD1234567890'; + private const AMOUNT_VALUE = 1000; + private const AMOUNT_CURRENCY = 'EUR'; + private const PAYMENT_BRAND = 'visa'; + + protected function setUp(): void + { + $this->authorizationHandlerMock = $this->createMock(AuthorizationHandler::class); + $this->adyenPaymentResponseCollectionMock = $this->createMock(AdyenPaymentResponseCollection::class); + $this->paymentMethodsMock = $this->createMock(PaymentMethods::class); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + + $this->paymentMock = $this->createMock(Payment::class); + + $this->orderMock = $this->createMock(Order::class); + $this->orderMock->method('getPayment')->willReturn($this->paymentMock); + $this->orderMock->method('getIncrementId')->willReturn(self::INCREMENT_ID); + + $this->observerMock = $this->createMock(Observer::class); + $this->observerMock->method('getData')->with('order')->willReturn($this->orderMock); + + $this->authorizeAfterOrderPlacement = new AuthorizeAfterOrderPlacement( + $this->authorizationHandlerMock, + $this->adyenPaymentResponseCollectionMock, + $this->paymentMethodsMock, + $this->adyenLoggerMock, + $this->orderRepositoryMock + ); + } + + private function buildPaymentResponse(string $resultCode, array $responseData): array + { + return [ + PaymentResponseInterface::RESULT_CODE => $resultCode, + 'response' => json_encode($responseData) + ]; + } + + private function buildAuthorisedResponseData(array $additionalData = []): array + { + return [ + 'paymentMethod' => ['brand' => self::PAYMENT_BRAND], + 'pspReference' => self::PSP_REFERENCE, + 'amount' => [ + 'value' => self::AMOUNT_VALUE, + 'currency' => self::AMOUNT_CURRENCY + ], + 'additionalData' => $additionalData + ]; + } + + public function testNonAdyenPaymentReturnsEarly(): void + { + $this->paymentMock->method('getMethod')->willReturn('checkmo'); + $this->paymentMethodsMock->method('isAdyenPayment')->with('checkmo')->willReturn(false); + + $this->adyenPaymentResponseCollectionMock + ->expects($this->never()) + ->method('getPaymentResponsesWithMerchantReferences'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testNoPaymentResponses(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->with('adyen_cc')->willReturn(true); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->with([self::INCREMENT_ID]) + ->willReturn([]); + + $this->authorizationHandlerMock->expects($this->never())->method('execute'); + $this->orderRepositoryMock->expects($this->never())->method('save'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testNonAuthorisedResponseIsSkipped(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $paymentResponse = $this->buildPaymentResponse('Refused', []); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->willReturn([$paymentResponse]); + + $this->authorizationHandlerMock->expects($this->never())->method('execute'); + $this->orderRepositoryMock->expects($this->never())->method('save'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testAuthorisedResponseProcessesAuthorization(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $additionalData = ['someKey' => 'someValue']; + $responseData = $this->buildAuthorisedResponseData($additionalData); + $paymentResponse = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->with([self::INCREMENT_ID]) + ->willReturn([$paymentResponse]); + + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->with( + $this->orderMock, + self::PAYMENT_BRAND, + self::PSP_REFERENCE, + self::AMOUNT_VALUE, + self::AMOUNT_CURRENCY, + $additionalData + ) + ->willReturn($this->orderMock); + + $this->orderRepositoryMock->expects($this->once()) + ->method('save') + ->with($this->orderMock); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testMultipleResponsesOnlyProcessesAuthorised(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $responseData = $this->buildAuthorisedResponseData(); + $authorisedResponse = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData); + $refusedResponse = $this->buildPaymentResponse('Refused', []); + $pendingResponse = $this->buildPaymentResponse('Pending', []); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->willReturn([$refusedResponse, $authorisedResponse, $pendingResponse]); + + $this->authorizationHandlerMock->expects($this->once()) + ->method('execute') + ->willReturn($this->orderMock); + + $this->orderRepositoryMock->expects($this->once())->method('save'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testMultipleAuthorisedResponsesAllProcessed(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $responseData1 = $this->buildAuthorisedResponseData(); + $responseData2 = $this->buildAuthorisedResponseData(['key' => 'val']); + $authorisedResponse1 = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData1); + $authorisedResponse2 = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData2); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->willReturn([$authorisedResponse1, $authorisedResponse2]); + + $this->authorizationHandlerMock->expects($this->exactly(2)) + ->method('execute') + ->willReturn($this->orderMock); + + $this->orderRepositoryMock->expects($this->exactly(2))->method('save'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testExceptionIsLoggedAndNotThrown(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $responseData = $this->buildAuthorisedResponseData(); + $paymentResponse = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->willReturn([$paymentResponse]); + + $exceptionMessage = 'Something went wrong'; + $this->authorizationHandlerMock->method('execute') + ->willThrowException(new Exception($exceptionMessage)); + + $this->adyenLoggerMock->expects($this->once()) + ->method('error') + ->with(sprintf( + 'Failed to process authorization after order placement for order #%s: %s', + self::INCREMENT_ID, + $exceptionMessage + )); + + $this->orderRepositoryMock->expects($this->never())->method('save'); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } + + public function testExceptionDuringOrderSaveIsLogged(): void + { + $this->paymentMock->method('getMethod')->willReturn('adyen_cc'); + $this->paymentMethodsMock->method('isAdyenPayment')->willReturn(true); + + $responseData = $this->buildAuthorisedResponseData(); + $paymentResponse = $this->buildPaymentResponse(PaymentResponseHandler::AUTHORISED, $responseData); + + $this->adyenPaymentResponseCollectionMock + ->method('getPaymentResponsesWithMerchantReferences') + ->willReturn([$paymentResponse]); + + $this->authorizationHandlerMock->method('execute')->willReturn($this->orderMock); + + $exceptionMessage = 'Could not save order'; + $this->orderRepositoryMock->method('save') + ->willThrowException(new Exception($exceptionMessage)); + + $this->adyenLoggerMock->expects($this->once()) + ->method('error') + ->with(sprintf( + 'Failed to process authorization after order placement for order #%s: %s', + self::INCREMENT_ID, + $exceptionMessage + )); + + $this->authorizeAfterOrderPlacement->execute($this->observerMock); + } +}