diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index 7402600e7..350a2cad1 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 1dbfb8a56..00797c909 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 767590005..d48c0843a 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:
diff --git a/Helper/AdyenOrderPayment.php b/Helper/AdyenOrderPayment.php
index d8c2336d6..e372cbc9f 100644
--- a/Helper/AdyenOrderPayment.php
+++ b/Helper/AdyenOrderPayment.php
@@ -192,30 +192,38 @@ 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 $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 $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();
$adyenOrderPayment = $this->adyenOrderPaymentFactory->create();
$adyenOrderPayment->setPspreference($pspReference);
- $adyenOrderPayment->setMerchantReference($merchantReference);
+ $adyenOrderPayment->setMerchantReference($order->getIncrementId());
$adyenOrderPayment->setPaymentId($payment->getId());
- $adyenOrderPayment->setPaymentMethod($notification->getPaymentMethod());
+ $adyenOrderPayment->setPaymentMethod($paymentMethod);
$adyenOrderPayment->setCaptureStatus($captureStatus);
$adyenOrderPayment->setAmount($amount);
$adyenOrderPayment->setTotalRefunded(0);
@@ -228,7 +236,7 @@ public function createAdyenOrderPayment(Order $order, Notification $notification
'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 1c89b72cc..30725e7f2 100644
--- a/Helper/Invoice.php
+++ b/Helper/Invoice.php
@@ -77,18 +77,23 @@ public function __construct(
/**
* @param Order $order
- * @param Notification $notification
* @param bool $isAutoCapture
+ * @param string $pspReference
+ * @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,
+ int $amountValue
+ ): ?InvoiceModel {
$this->adyenLogger->addAdyenNotification(
'Creating invoice for order',
[
- 'pspReference' => $notification->getPspreference(),
- 'merchantReference' => $notification->getMerchantReference()
+ 'pspReference' => $pspReference,
+ 'merchantReference' => $order->getIncrementId()
]
);
@@ -102,12 +107,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 +126,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,
+ $order->getIncrementId()
),
$this->adyenLogger->getInvoiceContext($invoice)
);
@@ -132,8 +136,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' => $order->getIncrementId()
]
);
@@ -153,9 +157,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 a416bbc3a..686f232ca 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,15 @@ 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 int $amount
* @return MagentoOrder
*/
- public function finalizeOrder(MagentoOrder $order, Notification $notification): MagentoOrder
- {
- $amount = $notification->getAmountValue();
+ public function finalizeOrder(
+ MagentoOrder $order,
+ string $pspReference,
+ int $amount
+ ): MagentoOrder {
$orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order, false);
$formattedOrderAmount = $this->dataHelper->formatAmount($orderAmountCurrency->getAmount(), $orderAmountCurrency->getCurrencyCode());
$fullAmountFinalized = $this->adyenOrderPaymentHelper->isFullAmountFinalized($order);
@@ -221,12 +224,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 +232,8 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification):
$formattedOrderAmount
),
[
- 'pspReference' => $notification->getPspreference(),
- 'merchantReference' => $notification->getMerchantReference()
+ 'pspReference' => $pspReference,
+ '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
@@ -248,7 +245,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 +255,8 @@ public function finalizeOrder(MagentoOrder $order, Notification $notification):
$order->getIncrementId()
),
[
- 'pspReference' => $notification->getPspreference(),
- 'merchantReference' => $notification->getMerchantReference()
+ 'pspReference' => $pspReference,
+ 'merchantReference' => $order->getIncrementId()
]);
}
} else {
diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php
index 6bc5fa214..fb1e7abcc 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/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php
index 0f1732208..ae08733f8 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(
@@ -319,6 +322,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 c5581daf0..1de8fe168 100644
--- a/Helper/Webhook/AuthorisationWebhookHandler.php
+++ b/Helper/Webhook/AuthorisationWebhookHandler.php
@@ -14,14 +14,12 @@
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\ResourceModel\Order\Payment as AdyenOrderPayment;
use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider;
use Adyen\Webhook\PaymentStates;
use Magento\Framework\Exception\LocalizedException;
@@ -32,30 +30,26 @@
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
+ * @param SerializerInterface $serializer
+ * @param AdyenOrderPayment $adyenOrderPaymentResourceModel
*/
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,
+ private readonly SerializerInterface $serializer,
+ private readonly AdyenOrderPayment $adyenOrderPaymentResourceModel
) { }
/**
@@ -84,78 +78,34 @@ 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);
-
- $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);
-
$payment = $order->getPayment();
- $payment->setAmountAuthorized($order->getGrandTotal());
- $payment->setBaseAmountAuthorized($order->getBaseGrandTotal());
- $this->cleanupAdditionalInformation->execute($payment);
+ /*
+ * 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)
+ );
+ }
- $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 +191,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
@@ -328,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 0375bf6a5..5f2f50e26 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 76a0ad49f..96b502e23 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
new file mode 100644
index 000000000..074e35aaf
--- /dev/null
+++ b/Model/AuthorizationHandler.php
@@ -0,0 +1,156 @@
+paymentMethodsHelper->isAutoCapture($order, $paymentMethod);
+
+ $this->adyenOrderPaymentHelper->createAdyenOrderPayment(
+ $order,
+ $isAutoCapture,
+ $pspReference,
+ $paymentMethod,
+ $amountValue,
+ $amountCurrency
+ );
+
+ if ($this->adyenOrderPaymentHelper->isFullAmountAuthorized($order)) {
+ $order = $this->orderHelper->setPrePaymentAuthorized($order);
+ $this->orderHelper->updatePaymentDetails($order, $pspReference);
+
+ $requireFraudManualReview = $this->caseManagementHelper->requiresManualReview($additionalData);
+
+ $order = $isAutoCapture
+ ? $this->handleAutoCapture(
+ $order,
+ $pspReference,
+ $amountValue,
+ $requireFraudManualReview
+ )
+ : $this->handleManualCapture($order, $pspReference, $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 int $amountValue
+ * @param bool $requireFraudManualReview
+ * @return Order
+ * @throws LocalizedException
+ */
+ private function handleAutoCapture(
+ Order $order,
+ string $pspReference,
+ int $amountValue,
+ bool $requireFraudManualReview
+ ): Order {
+ $this->invoiceHelper->createInvoice($order, true, $pspReference, $amountValue);
+ if ($requireFraudManualReview) {
+ $order = $this->caseManagementHelper->markCaseAsPendingReview($order, $pspReference, true);
+ } else {
+ $order = $this->orderHelper->finalizeOrder($order, $pspReference, $amountValue);
+ }
+
+ return $order;
+ }
+
+ /**
+ * @param Order $order
+ * @param string $pspReference
+ * @param bool $requireFraudManualReview
+ * @return Order
+ */
+ private function handleManualCapture(
+ Order $order,
+ string $pspReference,
+ 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' => $order->getIncrementId()
+ ]
+ );
+ }
+
+ return $order;
+ }
+
+ /**
+ * @param Order $order
+ * @param string $paymentMethod
+ *
+ * @return 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
+ if ($paymentMethod !== 'adyen_boleto' && !$order->getEmailSent()) {
+ $this->orderHelper->sendOrderMail($order);
+ }
+ }
+}
diff --git a/Observer/AuthorizeAfterOrderPlacement.php b/Observer/AuthorizeAfterOrderPlacement.php
new file mode 100644
index 000000000..6c9cbb9e3
--- /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/Test/Unit/Helper/AdyenOrderPaymentTest.php b/Test/Unit/Helper/AdyenOrderPaymentTest.php
index 37c9806f7..2b8488b15 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 7c8997910..28b53902c 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 514501ecc..084c7537f 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/PaymentMethodsTest.php b/Test/Unit/Helper/PaymentMethodsTest.php
index 2509583b6..456bb9bd9 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';
diff --git a/Test/Unit/Helper/PaymentResponseHandlerTest.php b/Test/Unit/Helper/PaymentResponseHandlerTest.php
index 738391bc9..26ceb877a 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
);
}
@@ -272,7 +276,7 @@ public function testHandlePaymentsDetailsResponseWithNullResultCode()
public function testHandlePaymentsDetailsResponseAuthorised()
{
$ccType = 'visa';
-
+
$paymentsDetailsResponse = [
'resultCode' => PaymentResponseHandler::AUTHORISED,
'pspReference' => 'ABC123456789',
@@ -287,7 +291,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
@@ -713,10 +721,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 4f367429e..d2f5340f1 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 3e7866a89..f2adb4d59 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);
diff --git a/Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php b/Test/Unit/Helper/Webhook/ManualReviewAcceptWebhookHandlerTest.php
new file mode 100644
index 000000000..3e213bb53
--- /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 000000000..f5b6352ad
--- /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 000000000..3bf5c8ca1
--- /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);
+ }
+}
diff --git a/etc/events.xml b/etc/events.xml
index 143c4cc82..229ca3d36 100644
--- a/etc/events.xml
+++ b/etc/events.xml
@@ -362,4 +362,7 @@
+
+
+