From 830d5e15a8db01fe64ba047fe70a94419d01eed1 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 18 Feb 2025 14:23:25 +0100 Subject: [PATCH 1/2] [ECP-9078] Implement is_open_invoice payment method configuration field (#2856) * [ECP-9078] Implement is_open_invoice payment method configuration field * [ECP-9078] Write unit tests and clean-up unused imports * [ECP-9078] Deprecate unused method * [ECP-9078] Write unit tests * [ECP-9078] Write unit tests * [ECP-9078] Ignore CS issue on the test file * [ECP-9078] Update unit tests * [ECP-9078] PHPCS ignore * [ECP-9078] PHPCS remove ignore argument --------- Co-authored-by: Can Demiralp --- .github/Makefile | 2 +- Gateway/Data/Order/OrderAdapter.php | 5 + Gateway/Request/CaptureDataBuilder.php | 57 ++-- Gateway/Request/CheckoutDataBuilder.php | 60 +--- Gateway/Request/RefundDataBuilder.php | 78 ++--- Helper/Data.php | 2 + Helper/PaymentMethods.php | 30 +- Helper/Util/PaymentMethodUtil.php | 55 +++- Helper/Webhook.php | 85 ++---- Observer/BeforeShipmentObserver.php | 11 +- .../Request/CaptureDataBuilderTest.php | 27 +- .../Request/CheckoutDataBuilderTest.php | 10 +- .../Gateway/Request/RefundDataBuilderTest.php | 286 ++++++++++++++++++ Test/Unit/Helper/PaymentMethodsTest.php | 29 +- Test/Unit/Helper/WebhookTest.php | 58 +++- .../Observer/BeforeShipmentObserverTest.php | 42 ++- etc/config.xml | 20 ++ 17 files changed, 640 insertions(+), 217 deletions(-) create mode 100644 Test/Unit/Gateway/Request/RefundDataBuilderTest.php diff --git a/.github/Makefile b/.github/Makefile index e0b1d2c469..8038ee0e6c 100644 --- a/.github/Makefile +++ b/.github/Makefile @@ -15,7 +15,7 @@ magento: install: composer config --json repositories.local '{"type": "path", "url": "/data/extensions/workdir", "options": { "symlink": false } }' composer require "adyen/module-payment:*" - vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml --error-severity=10 --ignore-annotations -n -p vendor/adyen/module-payment + vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml --error-severity=10 -n -p vendor/adyen/module-payment bin/magento module:enable Adyen_Payment bin/magento setup:upgrade bin/magento setup:di:compile diff --git a/Gateway/Data/Order/OrderAdapter.php b/Gateway/Data/Order/OrderAdapter.php index 1e909d9977..5b4c75a627 100644 --- a/Gateway/Data/Order/OrderAdapter.php +++ b/Gateway/Data/Order/OrderAdapter.php @@ -33,6 +33,11 @@ public function __construct( parent::__construct($order, $addressAdapterFactory); } + /** + * @deprecated Use Magento\Sales\Api\Data\OrderInterface::getQuoteId() instead + * + * @return float|int|null + */ public function getQuoteId() { return $this->order->getQuoteId(); diff --git a/Gateway/Request/CaptureDataBuilder.php b/Gateway/Request/CaptureDataBuilder.php index ff8ce9557a..b2dda81d27 100644 --- a/Gateway/Request/CaptureDataBuilder.php +++ b/Gateway/Request/CaptureDataBuilder.php @@ -18,10 +18,11 @@ use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Data as DataHelper; use Adyen\Payment\Helper\OpenInvoice; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\ResourceModel\Order\Payment; -use Adyen\Payment\Observer\AdyenPaymentMethodDataAssignObserver; use Magento\Framework\App\Action\Context; +use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; @@ -33,40 +34,36 @@ */ class CaptureDataBuilder implements BuilderInterface { - private DataHelper $adyenHelper; - private ChargedCurrency $chargedCurrency; - private Payment $orderPaymentResourceModel; - private AdyenOrderPayment $adyenOrderPaymentHelper; - private AdyenLogger $adyenLogger; - private Context $context; - protected OpenInvoice $openInvoiceHelper; - + /** + * @param DataHelper $adyenHelper + * @param ChargedCurrency $chargedCurrency + * @param AdyenOrderPayment $adyenOrderPaymentHelper + * @param AdyenLogger $adyenLogger + * @param Context $context + * @param Payment $orderPaymentResourceModel + * @param OpenInvoice $openInvoiceHelper + * @param PaymentMethods $paymentMethodsHelper + */ public function __construct( - DataHelper $adyenHelper, - ChargedCurrency $chargedCurrency, - AdyenOrderPayment $adyenOrderPaymentHelper, - AdyenLogger $adyenLogger, - Context $context, - Payment $orderPaymentResourceModel, - OpenInvoice $openInvoiceHelper - ) { - $this->adyenHelper = $adyenHelper; - $this->chargedCurrency = $chargedCurrency; - $this->adyenOrderPaymentHelper = $adyenOrderPaymentHelper; - $this->adyenLogger = $adyenLogger; - $this->context = $context; - $this->orderPaymentResourceModel = $orderPaymentResourceModel; - $this->openInvoiceHelper = $openInvoiceHelper; - } + private readonly DataHelper $adyenHelper, + private readonly ChargedCurrency $chargedCurrency, + private readonly AdyenOrderPayment $adyenOrderPaymentHelper, + private readonly AdyenLogger $adyenLogger, + private readonly Context $context, + private readonly Payment $orderPaymentResourceModel, + protected readonly OpenInvoice $openInvoiceHelper, + private readonly PaymentMethods $paymentMethodsHelper + ) { } /** - * @throws AdyenException + * @throws AdyenException|LocalizedException */ public function build(array $buildSubject): array { /** @var PaymentDataObject $paymentDataObject */ $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); + $paymentMethodInstance = $payment->getMethodInstance(); /** @var Order $order */ $order = $payment->getOrder(); /** @var Invoice $latestInvoice */ @@ -78,7 +75,6 @@ public function build(array $buildSubject): array $orderAmountCents = $this->adyenHelper->formatAmount($orderAmountCurrency->getAmount(), $currency); $pspReference = $payment->getCcTransId(); - $brandCode = $payment->getAdditionalInformation(AdyenPaymentMethodDataAssignObserver::BRAND_CODE); // If total amount has not been authorized if (!$this->adyenOrderPaymentHelper->isFullAmountAuthorized($order)) { @@ -116,7 +112,7 @@ public function build(array $buildSubject): array ]; //Check additionaldata - if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($brandCode)) { + if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) { $openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForInvoice($latestInvoice); $requestBody = array_merge($requestBody, $openInvoiceFields); } @@ -139,6 +135,7 @@ public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrd ), $this->adyenLogger->getOrderContext($payment->getOrder())); $captureAmountCents = $this->adyenHelper->formatAmount($captureAmount, $currency); + $paymentMethodInstance = $payment->getMethodInstance(); $captureData = []; $counterAmount = 0; $i = 0; @@ -174,9 +171,7 @@ public function buildPartialOrMultipleCaptureData($payment, $currency, $adyenOrd "paymentPspReference" => $adyenOrderPayment[OrderPaymentInterface::PSPREFRENCE] ]; - if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod( - $adyenOrderPayment[OrderPaymentInterface::PAYMENT_METHOD] - )) { + if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) { $order = $payment->getOrder(); $invoices = $order->getInvoiceCollection(); // The latest invoice will contain only the selected items(and quantities) for the (partial) capture diff --git a/Gateway/Request/CheckoutDataBuilder.php b/Gateway/Request/CheckoutDataBuilder.php index 4ba033008a..c6f8679643 100644 --- a/Gateway/Request/CheckoutDataBuilder.php +++ b/Gateway/Request/CheckoutDataBuilder.php @@ -14,12 +14,14 @@ use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\StateData; use Adyen\Payment\Helper\OpenInvoice; use Adyen\Payment\Model\Config\Source\ThreeDSFlow; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; use Adyen\Payment\Observer\AdyenCcDataAssignObserver; use Adyen\Payment\Observer\AdyenPaymentMethodDataAssignObserver; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; @@ -36,38 +38,9 @@ class CheckoutDataBuilder implements BuilderInterface self::ADYEN_BOLETO ]; - /** - * @var Data - */ - private Data $adyenHelper; - - /** - * @var CartRepositoryInterface - */ - private CartRepositoryInterface $cartRepository; - - /** - * @var ChargedCurrency - */ - private ChargedCurrency $chargedCurrency; - - /** - * @var StateData - */ - private StateData $stateData; - - /** - * @var Config - */ - private Config $configHelper; - - /** - * @var OpenInvoice - */ - private OpenInvoice $openInvoiceHelper; - /** * CheckoutDataBuilder constructor. + * * @param Data $adyenHelper * @param StateData $stateData * @param CartRepositoryInterface $cartRepository @@ -76,31 +49,26 @@ class CheckoutDataBuilder implements BuilderInterface * @param OpenInvoice $openInvoiceHelper */ public function __construct( - Data $adyenHelper, - StateData $stateData, - CartRepositoryInterface $cartRepository, - ChargedCurrency $chargedCurrency, - Config $configHelper, - OpenInvoice $openInvoiceHelper - ) { - $this->adyenHelper = $adyenHelper; - $this->stateData = $stateData; - $this->cartRepository = $cartRepository; - $this->chargedCurrency = $chargedCurrency; - $this->configHelper = $configHelper; - $this->openInvoiceHelper = $openInvoiceHelper; - } + private readonly Data $adyenHelper, + private readonly StateData $stateData, + private readonly CartRepositoryInterface $cartRepository, + private readonly ChargedCurrency $chargedCurrency, + private readonly Config $configHelper, + private readonly OpenInvoice $openInvoiceHelper, + private readonly PaymentMethods $paymentMethodsHelper + ) { } /** * @param array $buildSubject * @return array - * @throws NoSuchEntityException + * @throws NoSuchEntityException|LocalizedException */ public function build(array $buildSubject): array { /** @var PaymentDataObject $paymentDataObject */ $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); + $paymentMethodInstance = $payment->getMethodInstance(); /** @var Order $order */ $order = $payment->getOrder(); $storeId = $order->getStoreId(); @@ -130,7 +98,7 @@ public function build(array $buildSubject): array $brandCode = $payment->getAdditionalInformation(AdyenPaymentMethodDataAssignObserver::BRAND_CODE); if ( - (isset($brandCode) && $this->adyenHelper->isPaymentMethodOpenInvoiceMethod($brandCode)) || + $this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance) || $payment->getMethod() === AdyenPayByLinkConfigProvider::CODE ) { $openInvoiceFields = $this->openInvoiceHelper->getOpenInvoiceDataForOrder($order); diff --git a/Gateway/Request/RefundDataBuilder.php b/Gateway/Request/RefundDataBuilder.php index 6777a2e65f..3cfe64b006 100644 --- a/Gateway/Request/RefundDataBuilder.php +++ b/Gateway/Request/RefundDataBuilder.php @@ -11,13 +11,16 @@ namespace Adyen\Payment\Gateway\Request; +use Adyen\Payment\Api\Data\OrderPaymentInterface; use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\OpenInvoice; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Model\ResourceModel\Invoice\CollectionFactory; use Adyen\Payment\Model\ResourceModel\Order\Payment\CollectionFactory as PaymentCollectionFactory; -use Adyen\Payment\Observer\AdyenPaymentMethodDataAssignObserver; +use Magento\Framework\Exception\LocalizedException; +use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Sales\Model\Order\Payment; @@ -31,38 +34,42 @@ class RefundDataBuilder implements BuilderInterface const REFUND_STRATEGY_DESCENDING_ORDER = '2'; const REFUND_STRATEGY_BASED_ON_RATIO = '3'; - private Data $adyenHelper; - private Config $configHelper; - private PaymentCollectionFactory $orderPaymentCollectionFactory; - private ChargedCurrency $chargedCurrency; - private OpenInvoice $openInvoiceHelper; - + /** + * @param Data $adyenHelper + * @param PaymentCollectionFactory $orderPaymentCollectionFactory + * @param ChargedCurrency $chargedCurrency + * @param Config $configHelper + * @param OpenInvoice $openInvoiceHelper + * @param PaymentMethods $paymentMethodsHelper + */ public function __construct( - Data $adyenHelper, - PaymentCollectionFactory $orderPaymentCollectionFactory, - ChargedCurrency $chargedCurrency, - Config $configHelper, - OpenInvoice $openInvoiceHelper - ) { - $this->adyenHelper = $adyenHelper; - $this->orderPaymentCollectionFactory = $orderPaymentCollectionFactory; - $this->chargedCurrency = $chargedCurrency; - $this->configHelper = $configHelper; - $this->openInvoiceHelper = $openInvoiceHelper; - } - + private readonly Data $adyenHelper, + private readonly PaymentCollectionFactory $orderPaymentCollectionFactory, + private readonly ChargedCurrency $chargedCurrency, + private readonly Config $configHelper, + private readonly OpenInvoice $openInvoiceHelper, + private readonly PaymentMethods $paymentMethodsHelper + ) { } + + /** + * @param array $buildSubject + * @return array + * @throws LocalizedException + */ public function build(array $buildSubject): array { + /** @var PaymentDataObject $paymentDataObject */ $paymentDataObject = SubjectReader::readPayment($buildSubject); - $order = $paymentDataObject->getOrder(); - /** @var Payment $payment */ + /** @var Payment $payment */ $payment = $paymentDataObject->getPayment(); - $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($payment->getOrder(), false); + $order = $payment->getOrder(); + $paymentMethodInstance = $payment->getMethodInstance(); + $orderAmountCurrency = $this->chargedCurrency->getOrderAmountCurrency($order, false); // Construct AdyenAmountCurrency from creditmemo $creditMemo = $payment->getCreditMemo(); - $creditMemoAmountCurrency = $this->chargedCurrency->getCreditMemoAmountCurrency($creditMemo, false); + $creditMemoAmountCurrency = $this->chargedCurrency->getCreditMemoAmountCurrency($creditMemo); $pspReference = $payment->getCcTransId(); $currency = $creditMemoAmountCurrency->getCurrencyCode(); @@ -70,7 +77,7 @@ public function build(array $buildSubject): array //Get Merchant Account - $storeId = $order ->getStoreId(); + $storeId = $order->getStoreId(); $method = $payment->getMethod(); if (isset($method) && $method === 'adyen_moto') { @@ -82,10 +89,10 @@ public function build(array $buildSubject): array // check if it contains a partial payment $orderPaymentCollection = $this->orderPaymentCollectionFactory ->create() - ->addFieldToFilter('payment_id', $payment->getId()); + ->addFieldToFilter(OrderPaymentInterface::PAYMENT_ID, $payment->getId()); // partial refund if multiple payments check refund strategy - if ($orderPaymentCollection->getSize() > self::REFUND_STRATEGY_ASCENDING_ORDER) { + if ($orderPaymentCollection->getSize() > 1) { $refundStrategy = $this->configHelper->getAdyenAbstractConfigData( 'partial_payments_refund_strategy', $storeId @@ -141,7 +148,7 @@ public function build(array $buildSubject): array $requestBody[] = [ "merchantAccount" => $merchantAccount, "amount" => $modificationAmountObject, - "reference" => $payment->getOrder()->getIncrementId(), + "reference" => $order->getIncrementId(), "paymentPspReference" => $partialPayment->getPspreference(), ]; } @@ -155,32 +162,27 @@ public function build(array $buildSubject): array [ "merchantAccount" => $merchantAccount, "amount" => $modificationAmount, - "reference" => $payment->getOrder()->getIncrementId(), + "reference" => $order->getIncrementId(), "paymentPspReference" => $pspReference, ] ]; - $brandCode = $payment->getAdditionalInformation( - AdyenPaymentMethodDataAssignObserver::BRAND_CODE - ); - - if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($brandCode)) { + if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) { $openInvoiceFieldsCreditMemo = $this->openInvoiceHelper->getOpenInvoiceDataForCreditMemo($creditMemo); //There is only one payment, so we add the fields to the first(and only) result - $requestBody[0] = array_merge($requestBody[0], $openInvoiceFieldsCreditMemo); + $requestBody[0] = array_merge($requestBody[0], $openInvoiceFieldsCreditMemo); } } - $request['clientConfig'] = ["storeId" => $payment->getOrder()->getStoreId()]; + $request['clientConfig'] = ["storeId" => $storeId]; $request['body'] = $requestBody; $request['headers'] = [ 'idempotencyExtraData' => [ - 'totalRefunded' => $payment->getOrder()->getTotalRefunded() ?? 0 + 'totalRefunded' => $order->getTotalRefunded() ?? 0 ] ]; return $request; } - } diff --git a/Helper/Data.php b/Helper/Data.php index 2e7a5f1b4b..b5116c9560 100755 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -680,6 +680,8 @@ public function getModuleVersion() } /** + * @deprecated Use Adyen\Payment\Helper\PaymentMethods::isOpenInvoice() instead. + * * @param $paymentMethod * @return bool */ diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php index ed057ca4e6..5c8b30f62e 100644 --- a/Helper/PaymentMethods.php +++ b/Helper/PaymentMethods.php @@ -66,6 +66,7 @@ class PaymentMethods extends AbstractHelper const FUNDING_SOURCE_CREDIT = 'credit'; const ADYEN_GROUP_ALTERNATIVE_PAYMENT_METHODS = 'adyen-alternative-payment-method'; + const CONFIG_FIELD_IS_OPEN_INVOICE = 'is_open_invoice'; const VALID_CHANNELS = ["iOS", "Android", "Web"]; @@ -638,7 +639,7 @@ protected function showLogosPaymentMethods(array $paymentMethods, array $payment $paymentMethodsExtraDetails[$paymentMethodCode]['icon'] = $icon; - //todo check if it is needed + // TODO::This field is not relevant anymore and can be removed during cleaning-up deprecated methods on V10. // check if payment method is an open invoice method $paymentMethodsExtraDetails[$paymentMethodCode]['isOpenInvoice'] = $this->adyenHelper->isPaymentMethodOpenInvoiceMethod($paymentMethodCode); @@ -767,12 +768,20 @@ public function getCcAvailableTypesByAlt(): array } /** - * @param Order $order - * @param string $notificationPaymentMethod + * Checks whether if the capture mode is auto on an order with the given notification `paymentMethod`. + * Note that, only a `notificationPaymentMethod` related to the order should be provided. + * + * @param Order $order Order object + * @param string $notificationPaymentMethod `paymentMethod` provided on the webhook of the given order * @return bool */ public function isAutoCapture(Order $order, string $notificationPaymentMethod): bool { + // TODO::Add a validation checking `$notificationPaymentMethod` belongs to the correct order (webhook) or not. + + $payment = $order->getPayment(); + $paymentMethodInstance = $payment->getMethodInstance(); + // validate if payment methods allows manual capture if (PaymentMethodUtil::isManualCaptureSupported($notificationPaymentMethod)) { $captureMode = trim( @@ -854,7 +863,7 @@ public function isAutoCapture(Order $order, string $notificationPaymentMethod): } // if auto capture mode for openinvoice is turned on then use auto capture - if ($autoCaptureOpenInvoice && $this->adyenHelper->isPaymentMethodOpenInvoiceMethod($notificationPaymentMethod)) { + if ($autoCaptureOpenInvoice && $this->isOpenInvoice($paymentMethodInstance)) { $this->adyenLogger->addAdyenNotification( 'This payment method is configured to be working as auto capture ', array_merge( @@ -905,7 +914,7 @@ public function isAutoCapture(Order $order, string $notificationPaymentMethod): * online capture after delivery, use Magento backend to online invoice * (if the option auto capture mode for openinvoice is not set) */ - if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod($notificationPaymentMethod)) { + if ($this->isOpenInvoice($paymentMethodInstance)) { $this->adyenLogger->addAdyenNotification( 'Capture mode for klarna is by default set to manual', array_merge( @@ -1061,4 +1070,15 @@ public function buildPaymentMethodIcon(string $paymentMethodCode, array $params) return ['url' => $url, 'width' => 77, 'height' => 50]; } + + /** + * Checks whether if the payment method is open invoice or not based on `is_open_invoice` configuration field. + * + * @param MethodInterface $paymentMethodInstance + * @return bool + */ + public function isOpenInvoice(MethodInterface $paymentMethodInstance): bool + { + return boolval($paymentMethodInstance->getConfigData(self::CONFIG_FIELD_IS_OPEN_INVOICE)); + } } diff --git a/Helper/Util/PaymentMethodUtil.php b/Helper/Util/PaymentMethodUtil.php index f8d182889c..d725e9bcaa 100644 --- a/Helper/Util/PaymentMethodUtil.php +++ b/Helper/Util/PaymentMethodUtil.php @@ -74,9 +74,50 @@ class PaymentMethodUtil 'elo_amazonpay', 'jcb_amazonpay', 'bcmc', - 'bcmc_mobile' + 'bcmc_mobile', + 'affirm', + 'afterpay', + 'afterpay_b2b', + 'afterpay_default', + 'afterpay_directdebit', + 'afterpaytouch', + 'afterpaytouch_AU', + 'afterpaytouch_CA', + 'afterpaytouch_NZ', + 'afterpaytouch_US', + 'clearpay', + 'facilypay', + 'facilypay_10x', + 'facilypay_10x_merchant_pays', + 'facilypay_10x_withfees', + 'facilypay_12x', + 'facilypay_12x_merchant_pays', + 'facilypay_12x_withfees', + 'facilypay_3x', + 'facilypay_3x_merchant_pays', + 'facilypay_3x_withfees', + 'facilypay_4x', + 'facilypay_4x_merchant_pays', + 'facilypay_4x_withfees', + 'facilypay_6x', + 'facilypay_6x_merchant_pays', + 'facilypay_6x_withfees', + 'facilypay_fr', + 'klarna', + 'klarna_b2b', + 'klarna_paynow', + 'klarna_account', + 'ratepay', + 'ratepay_directdebit', + 'walley', + 'walley_b2b' ]; + /** + * @deprecated Use payment method configuration field `is_open_invoice` defined in `Adyen\Payment\etc\config.xml`. + * + * @var string[] + */ const OPEN_INVOICE_PAYMENT_METHODS = [ 'affirm', 'afterpay', @@ -116,12 +157,12 @@ class PaymentMethodUtil 'walley_b2b' ]; - public static function isManualCaptureSupported($paymentMethod): bool + /** + * @param string $paymentMethod + * @return bool + */ + public static function isManualCaptureSupported(string $paymentMethod): bool { - if (self::isOpenInvoicePaymentMethod($paymentMethod)) { - return true; - } - // Check for payment methods with no variants if (in_array($paymentMethod, self::MANUAL_CAPTURE_SUPPORTED_PAYMENT_METHODS)) { return true; @@ -139,6 +180,8 @@ public static function isManualCaptureSupported($paymentMethod): bool } /** + * @deprecated Use Adyen\Payment\Helper\PaymentMethods::isOpenInvoice() method instead. + * * @param $paymentMethod * @return bool */ diff --git a/Helper/Webhook.php b/Helper/Webhook.php index 2b0a72e900..ddaac33fbf 100644 --- a/Helper/Webhook.php +++ b/Helper/Webhook.php @@ -23,7 +23,6 @@ use Adyen\Webhook\Notification as WebhookNotification; use Adyen\Webhook\PaymentStates; use Adyen\Webhook\Processor\ProcessorFactory; -use DateTime; use Exception; use Adyen\Payment\Model\Notification as NotificationEntity; use Magento\Framework\Serialize\SerializerInterface; @@ -52,59 +51,37 @@ class Webhook 'payment_authorized' => [Order::STATE_PROCESSING] ]; - /** - * @var AdyenLogger - */ - private $logger; - /** @var OrderHelper */ - private $orderHelper; - /** @var OrderRepository */ - private $orderRepository; - /** - * @var Data - */ - private $adyenHelper; - /** - * @var SerializerInterface - */ - private $serializer; - /** - * @var TimezoneInterface - */ - private $timezone; - /** - * @var ConfigHelper - */ - private $configHelper; - /** - * @var ChargedCurrency - */ - private $chargedCurrency; + // TODO::This property is not written but only is read. Check the usage. private $boletoPaidAmount; - private $klarnaReservationNumber; - private $ratepayDescriptor; - private $webhookHandlerFactory; + private ?string $klarnaReservationNumber; + private ?string $ratepayDescriptor; + /** + * @param Data $adyenHelper + * @param SerializerInterface $serializer + * @param TimezoneInterface $timezone + * @param Config $configHelper + * @param ChargedCurrency $chargedCurrency + * @param AdyenLogger $logger + * @param WebhookHandlerFactory $webhookHandlerFactory + * @param OrderHelper $orderHelper + * @param OrderRepository $orderRepository + * @param PaymentMethods $paymentMethodsHelper + */ public function __construct( - Data $adyenHelper, - SerializerInterface $serializer, - TimezoneInterface $timezone, - ConfigHelper $configHelper, - ChargedCurrency $chargedCurrency, - AdyenLogger $logger, - WebhookHandlerFactory $webhookHandlerFactory, - OrderHelper $orderHelper, - OrderRepository $orderRepository + private readonly Data $adyenHelper, + private readonly SerializerInterface $serializer, + private readonly TimezoneInterface $timezone, + private readonly ConfigHelper $configHelper, + private readonly ChargedCurrency $chargedCurrency, + private readonly AdyenLogger $logger, + private readonly WebhookHandlerFactory $webhookHandlerFactory, + private readonly OrderHelper $orderHelper, + private readonly OrderRepository $orderRepository, + private readonly PaymentMethods $paymentMethodsHelper ) { - $this->adyenHelper = $adyenHelper; - $this->serializer = $serializer; - $this->timezone = $timezone; - $this->configHelper = $configHelper; - $this->chargedCurrency = $chargedCurrency; - $this->logger = $logger; - $this->orderHelper = $orderHelper; - $this->orderRepository = $orderRepository; - $this->webhookHandlerFactory = $webhookHandlerFactory; + $this->klarnaReservationNumber = null; + $this->ratepayDescriptor = null; } /** @@ -314,6 +291,9 @@ private function addNotificationDetailsHistoryComment(Order $order, Notification $reason = $notification->getReason(); $success = (!empty($reason)) ? "$successResult
reason:$reason" : $successResult; + $payment = $order->getPayment(); + $paymentMethodInstance = $payment->getMethodInstance(); + $eventCode = $notification->getEventCode(); if ($eventCode == Notification::REFUND || $eventCode == Notification::CAPTURE) { // check if it is a full or partial refund @@ -342,9 +322,8 @@ private function addNotificationDetailsHistoryComment(Order $order, Notification } // if payment method is klarna, ratepay or openinvoice/afterpay show the reservartion number - if ($this->adyenHelper->isPaymentMethodOpenInvoiceMethod( - $notification->getPaymentMethod() - ) && !empty($this->klarnaReservationNumber)) { + if ($this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance) && + !empty($this->klarnaReservationNumber)) { $klarnaReservationNumberText = "
reservationNumber: " . $this->klarnaReservationNumber; } else { $klarnaReservationNumberText = ""; diff --git a/Observer/BeforeShipmentObserver.php b/Observer/BeforeShipmentObserver.php index 9d2795121b..e22097b62e 100644 --- a/Observer/BeforeShipmentObserver.php +++ b/Observer/BeforeShipmentObserver.php @@ -30,6 +30,8 @@ class BeforeShipmentObserver extends AbstractDataAssignObserver const ONSHIPMENT_CAPTURE_OPENINVOICE = 'onshipment'; /** + * @deprecated This property is not being used and will be removed on V10. + * * @var AdyenHelper */ private AdyenHelper $adyenHelper; @@ -86,7 +88,9 @@ public function execute(Observer $observer): void /** @var Shipment $shipment */ $shipment = $observer->getEvent()->getData('shipment'); $order = $shipment->getOrder(); - $paymentMethod = $order->getPayment()->getMethod(); + $payment = $order->getPayment(); + $paymentMethod = $payment->getMethod(); + $paymentMethodInstance = $payment->getMethodInstance(); if (!$this->paymentMethodsHelper->isAdyenPayment($paymentMethod)) { $this->logger->info( @@ -111,10 +115,7 @@ public function execute(Observer $observer): void return; } - $payment = $order->getPayment(); - $brandCode = $payment->getAdditionalInformation(AdyenPaymentMethodDataAssignObserver::BRAND_CODE); - - if (!$this->adyenHelper->isPaymentMethodOpenInvoiceMethod($brandCode)) { + if (!$this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstance)) { $this->logger->info( "Payment method is from Adyen but isn't OpenInvoice for order id {$order->getId()}", ['observer' => 'BeforeShipmentObserver'] diff --git a/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php b/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php index 36b49ca36e..095b235188 100644 --- a/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php +++ b/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php @@ -20,6 +20,7 @@ use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\Data as DataHelper; use Adyen\Payment\Helper\OpenInvoice; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\AdyenAmountCurrency; use Adyen\Payment\Model\ResourceModel\Order\Payment; @@ -27,6 +28,7 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\Message\ManagerInterface; use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Payment\Model\MethodInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\ResourceModel\Order\Invoice\Collection as InvoiceCollection; @@ -79,8 +81,7 @@ public static function adyenOrderPaymentsProvider(): array */ public function testBuildCaptureRequest($adyenOrderPayments, $fullAmountAuthorized) { - $adyenHelperMock = $this->createPartialMock(Data::class, ['isPaymentMethodOpenInvoiceMethod']); - $adyenHelperMock->method('isPaymentMethodOpenInvoiceMethod')->willReturn(true); + $adyenHelperMock = $this->createPartialMock(Data::class, []); $lineItems = [ 'id' => PHP_INT_MAX @@ -100,14 +101,23 @@ public function testBuildCaptureRequest($adyenOrderPayments, $fullAmountAuthoriz 'getIncrementId' => '00000000001', 'getTotalInvoiced' => 0 ]); + + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $paymentMock = $this->createConfiguredMock(\Magento\Sales\Model\Order\Payment::class, [ 'getOrder' => $orderMock, + 'getMethodInstance' => $paymentMethodInstanceMock, 'getCcTransId' => 'ABC123456789XYZ' ]); $paymentDataObjectMock = $this->createConfiguredMock(PaymentDataObject::class, [ 'getPayment' => $paymentMock ]); + $paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + $paymentMethodsHelperMock->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) + ->willReturn(true); + $chargedCurrencyHelperMock = $this->createConfiguredMock(ChargedCurrency::class, [ 'getInvoiceAmountCurrency' => $this->createConfiguredMock(AdyenAmountCurrency::class, [ 'getCurrencyCode' => 'EUR', @@ -137,7 +147,8 @@ public function testBuildCaptureRequest($adyenOrderPayments, $fullAmountAuthoriz $adyenOrderPaymentHelperMock, null, null, - $openInvoiceHelperMock + $openInvoiceHelperMock, + $paymentMethodsHelperMock ); $request = $captureDataBuilder->build($buildSubject); @@ -173,7 +184,8 @@ private function buildCaptureDataBuilderObject( $adyenOrderPaymentHelperMock = null, $adyenLoggerMock = null, $contextMock = null, - $openInvoiceHelperMock = null + $openInvoiceHelperMock = null, + $paymentMethodsHelperMock = null ): CaptureDataBuilder { if (is_null($adyenHelperMock)) { $adyenHelperMock = $this->createPartialMock(DataHelper::class, []); @@ -205,6 +217,10 @@ private function buildCaptureDataBuilderObject( $openInvoiceHelperMock = $this->createMock(OpenInvoice::class); } + if (is_null($paymentMethodsHelperMock)) { + $paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + } + return new CaptureDataBuilder( $adyenHelperMock, $chargedCurrencyMock, @@ -212,7 +228,8 @@ private function buildCaptureDataBuilderObject( $adyenLoggerMock, $contextMock, $orderPaymentResourceModelMock, - $openInvoiceHelperMock + $openInvoiceHelperMock, + $paymentMethodsHelperMock ); } } diff --git a/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php b/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php index de72e28846..b532f190bd 100644 --- a/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php +++ b/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php @@ -7,10 +7,12 @@ use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\OpenInvoice; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\StateData; use Adyen\Payment\Model\Config\Source\ThreeDSFlow; use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Payment\Model\MethodInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; @@ -26,6 +28,7 @@ class CheckoutDataBuilderTest extends AbstractAdyenTestCase protected ChargedCurrency|MockObject $chargedCurrencyMock; protected Config|MockObject $configMock; protected OpenInvoice|MockObject $openInvoiceMock; + protected PaymentMethods|MockObject $paymentMethodsHelperMock; public function setUp(): void { @@ -35,6 +38,7 @@ public function setUp(): void $this->chargedCurrencyMock = $this->createMock(ChargedCurrency::class); $this->configMock = $this->createMock(Config::class); $this->openInvoiceMock = $this->createMock(OpenInvoice::class); + $this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); $this->checkoutDataBuilder = new CheckoutDataBuilder( $this->adyenHelperMock, @@ -42,7 +46,8 @@ public function setUp(): void $this->cartRepositoryMock, $this->chargedCurrencyMock, $this->configMock, - $this->openInvoiceMock + $this->openInvoiceMock, + $this->paymentMethodsHelperMock ); parent::setUp(); @@ -62,8 +67,11 @@ public function testAllowThreeDSFlag() $orderMock->method('getQuoteId')->willReturn(1); $orderMock->method('getStoreId')->willReturn($storeId); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $paymentMock = $this->createMock(Payment::class); $paymentMock->method('getOrder')->willReturn($orderMock); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $buildSubject = [ 'payment' => $this->createConfiguredMock(PaymentDataObject::class, [ diff --git a/Test/Unit/Gateway/Request/RefundDataBuilderTest.php b/Test/Unit/Gateway/Request/RefundDataBuilderTest.php new file mode 100644 index 0000000000..163b0f6488 --- /dev/null +++ b/Test/Unit/Gateway/Request/RefundDataBuilderTest.php @@ -0,0 +1,286 @@ + + */ + +namespace Adyen\Payment\Test\Gateway\Request; + +use Adyen\Payment\Api\Data\OrderPaymentInterface; +use Adyen\Payment\Gateway\Request\RefundDataBuilder; +use Adyen\Payment\Helper\ChargedCurrency; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\OpenInvoice; +use Adyen\Payment\Helper\PaymentMethods; +use Adyen\Payment\Model\AdyenAmountCurrency; +use Adyen\Payment\Model\Order\Payment as AdyenOrderPayment; +use Adyen\Payment\Model\ResourceModel\Order\Payment\Collection; +use Adyen\Payment\Model\ResourceModel\Order\Payment\CollectionFactory as PaymentCollectionFactory; +use Adyen\Payment\Test\Unit\AbstractAdyenTestCase; +use Magento\Framework\Exception\LocalizedException; +use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Payment\Model\MethodInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; + +class RefundDataBuilderTest extends AbstractAdyenTestCase +{ + protected ?RefundDataBuilder $refundDataBuilder; + protected Data $adyenHelperMock; + protected PaymentCollectionFactory $orderPaymentCollectionFactoryMock; + protected ChargedCurrency $chargedCurrencyMock; + protected Config $configHelperMock; + protected OpenInvoice $openInvoiceHelperMock; + protected PaymentMethods $paymentMethodsHelperMock; + + /** + * @return void + */ + protected function setUp(): void + { + $this->adyenHelperMock = $this->createPartialMock(Data::class, [ + 'getAdyenMerchantAccount' + ]); + $this->orderPaymentCollectionFactoryMock = + $this->createGeneratedMock(PaymentCollectionFactory::class, ['create']); + $this->chargedCurrencyMock = $this->createMock(ChargedCurrency::class); + $this->configHelperMock = $this->createMock(Config::class); + $this->openInvoiceHelperMock = $this->createMock(OpenInvoice::class); + $this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + + $this->refundDataBuilder = new RefundDataBuilder( + $this->adyenHelperMock, + $this->orderPaymentCollectionFactoryMock, + $this->chargedCurrencyMock, + $this->configHelperMock, + $this->openInvoiceHelperMock, + $this->paymentMethodsHelperMock + ); + } + + /** + * @return void + */ + protected function tearDown(): void + { + $this->refundDataBuilder = null; + } + + private static function dataProviderForRefundDataBuilder(): array + { + return [ + [ + 'paymentMethod' => 'adyen_cc', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0 + ] + ] + ], + [ + 'paymentMethod' => 'adyen_moto', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0 + ] + ] + ], + [ + 'paymentMethod' => 'adyen_cc', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 75.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ] + ], + 'refundStrategy' => '1' + ], + [ + 'paymentMethod' => 'adyen_cc', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 75.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ] + ], + 'refundStrategy' => '2' + ], + [ + 'paymentMethod' => 'adyen_cc', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 75.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 25.00, + 'totalRefunded' => 25.0, + 'pspreference' => 'mock_pspreference' + ] + ], + 'refundStrategy' => '3' + ], + [ + 'paymentMethod' => 'adyen_cc', + 'orderPaymentCollectionData' => [ + [ + 'amount' => 100.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 75.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ], + [ + 'amount' => 300.00, + 'totalRefunded' => 0.0, + 'pspreference' => 'mock_pspreference' + ] + ], + 'refundStrategy' => '3' + ] + ]; + } + + /** + * @dataProvider dataProviderForRefundDataBuilder() + * + * @param string $paymentMethod + * @param array $orderPaymentCollectionData + * @param string|null $refundStrategy + * @return void + * @throws LocalizedException + */ + public function testBuild( + string $paymentMethod, + array $orderPaymentCollectionData, + string $refundStrategy = null + ) { + $storeId = 1; + $paymentId = 10; + $orderIncrementId = '0000000000101'; + $pspreference = 'XYZ123456789'; + $merchantAccount = 'mock_merchant_account'; + $creditMemoCurrency = 'EUR'; + $creditMemoAmount = 150.00; + $orderAmount = 200.00; + + $this->configHelperMock->method('getAdyenAbstractConfigData') + ->with('partial_payments_refund_strategy', $storeId) + ->willReturn($refundStrategy); + + $creditMemoMock = $this->createMock(Order\Creditmemo::class); + + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + + $orderMock = $this->createMock(Order::class); + $orderMock->method('getStoreId')->willReturn($storeId); + $orderMock->method('getIncrementId')->willReturn($orderIncrementId); + + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getId')->willReturn($paymentId); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); + $paymentMock->method('getOrder')->willReturn($orderMock); + $paymentMock->method('getCreditmemo')->willReturn($creditMemoMock); + $paymentMock->method('getMethod')->willReturn($paymentMethod); + $paymentMock->method('getCcTransId')->willReturn($pspreference); + $paymentMock->method('getAdditionalInformation') + ->with('motoMerchantAccount') + ->willReturn($merchantAccount); + + $orderAmountCurrencyMock = $this->createMock(AdyenAmountCurrency::class); + $orderAmountCurrencyMock->method('getAmount')->willReturn($orderAmount); + + $creditMemoAmountCurrencyMock = $this->createMock(AdyenAmountCurrency::class); + $creditMemoAmountCurrencyMock->method('getCurrencyCode')->willReturn($creditMemoCurrency); + $creditMemoAmountCurrencyMock->method('getAmount')->willReturn($creditMemoAmount); + + $this->chargedCurrencyMock->method('getOrderAmountCurrency') + ->with($orderMock) + ->willReturn($orderAmountCurrencyMock); + + $this->chargedCurrencyMock->method('getCreditMemoAmountCurrency') + ->with($creditMemoMock) + ->willReturn($creditMemoAmountCurrencyMock); + + $this->adyenHelperMock->method('getAdyenMerchantAccount') + ->with($paymentMethod, $storeId) + ->willReturn($merchantAccount); + + $orderPaymentCollectionMock = $this->createMock(Collection::class); + $orderPaymentCollectionMock->method('addFieldToFilter') + ->with(OrderPaymentInterface::PAYMENT_ID, $paymentId) + ->willReturnSelf(); + $orderPaymentCollectionMock->method('getSize')->willReturn(count($orderPaymentCollectionData)); + + if (count($orderPaymentCollectionData) > 1) { + $objectArray = []; + + foreach ($orderPaymentCollectionData as $orderPayment) { + $objectArray[] = $this->createConfiguredMock(AdyenOrderPayment::class, [ + 'getAmount' => $orderPayment['amount'], + 'getTotalRefunded' => $orderPayment['totalRefunded'], + 'getPspreference' => $orderPayment['pspreference'] + ]); + } + + // phpcs:ignore + $orderPaymentCollectionMock->method('getIterator')->willReturn(new \ArrayObject($objectArray)); + } + + $this->orderPaymentCollectionFactoryMock->method('create')->willReturn($orderPaymentCollectionMock); + + $this->paymentMethodsHelperMock->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) + ->willReturn(true); + + $this->openInvoiceHelperMock->method('getOpenInvoiceDataForCreditMemo') + ->with($creditMemoMock) + ->willReturn(['lineItems' => [['product_id' => 1]]]); + + $buildSubject = [ + 'payment' => $this->createConfiguredMock(PaymentDataObject::class, [ + 'getPayment' => $paymentMock + ]) + ]; + + $result = $this->refundDataBuilder->build($buildSubject); + $this->assertIsArray($result); + $this->assertArrayHasKey('clientConfig', $result); + $this->assertEquals($storeId, $result['clientConfig']['storeId']); + $this->assertArrayHasKey('body', $result); + $this->assertEquals(count($orderPaymentCollectionData), count($result['body'])); + $this->assertArrayHasKey('merchantAccount', $result['body'][0]); + $this->assertArrayHasKey('amount', $result['body'][0]); + $this->assertArrayHasKey('reference', $result['body'][0]); + $this->assertArrayHasKey('paymentPspReference', $result['body'][0]); + } +} diff --git a/Test/Unit/Helper/PaymentMethodsTest.php b/Test/Unit/Helper/PaymentMethodsTest.php index 3aa38586da..17ebcfb1c6 100644 --- a/Test/Unit/Helper/PaymentMethodsTest.php +++ b/Test/Unit/Helper/PaymentMethodsTest.php @@ -956,12 +956,20 @@ public function testIsAutoCapture( $paymentCode, $autoCaptureOpenInvoice, $manualCapturePayPal, - $expectedResult + $expectedResult, + $isOpenInvoicePaymentMethod ) { // Reset Config mock to prevent interventions with other expects() assertions. $this->configHelperMock = $this->createMock(Config::class); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $paymentMethodInstanceMock->method('getConfigData')->with(PaymentMethods::CONFIG_FIELD_IS_OPEN_INVOICE)->willReturn($isOpenInvoicePaymentMethod); + + $paymentMock = $this->createMock(Order\Payment::class); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); + $this->orderMock->method('getStoreId')->willReturn(1); + $this->orderMock->method('getPayment')->willReturn($paymentMock); $this->configHelperMock->expects($this->any()) ->method('getConfigData') @@ -1004,11 +1012,13 @@ public function autoCaptureDataProvider(): array { return [ // Manual capture supported, capture mode manual, sepa flow not authcap - [true, 'manual', 'notauthcap', 'paypal', true, null, true], + [true, 'manual', 'notauthcap', 'paypal', true, null, true, false], // Manual capture supported, capture mode auto - [true, 'auto', '', 'sepadirectdebit', true, null, true], + [true, 'auto', '', 'sepadirectdebit', true, null, true, false], + // Manual capture supported open invoice + [true, 'manual', '', 'klarna', false, null, false, true], // Manual capture not supported - [false, '', '', 'sepadirectdebit', true, null, true] + [false, '', '', 'sepadirectdebit', true, null, true, false] ]; } @@ -1315,4 +1325,15 @@ public function testRemovePaymentMethodsActivation() $this->paymentMethodsHelper->removePaymentMethodsActivation('default', 0); } + + public function testIsOpenInvoice() + { + $paymentMethodInstaceMock = $this->createMock(MethodInterface::class); + $paymentMethodInstaceMock->method('getConfigData') + ->with(PaymentMethods::CONFIG_FIELD_IS_OPEN_INVOICE) + ->willReturn(true); + + $result = $this->paymentMethodsHelper->isOpenInvoice($paymentMethodInstaceMock); + $this->assertTrue($result); + } } diff --git a/Test/Unit/Helper/WebhookTest.php b/Test/Unit/Helper/WebhookTest.php index a0bfa9d125..f9783bd783 100644 --- a/Test/Unit/Helper/WebhookTest.php +++ b/Test/Unit/Helper/WebhookTest.php @@ -1,12 +1,14 @@ method('getPspreference')->willReturn('ABCD1234GHJK5678'); $notification->method('getPaymentMethod')->willReturn('ADYEN_CC'); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $payment = $this->createMock(Payment::class); + $payment->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); + $order = $this->createMock(Order::class); $order->method('getState')->willReturn(Order::STATE_NEW); $order->method('getIncrementId')->willReturn(123); @@ -106,7 +111,8 @@ public function testProcessNotificationForInvalidDataException() $logger, $webhookHandlerFactory, $orderHelper, - $this->createMock(OrderRepository::class) + $this->createMock(OrderRepository::class), + $this->createMock(PaymentMethods::class) ]) ->onlyMethods([ 'updateNotification', @@ -125,9 +131,15 @@ public function testProcessNotificationForInvalidDataException() public function testAddNotificationDetailsHistoryComment() { + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); + $orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); + $orderMock->method('getPayment')->willReturn($paymentMock); $notificationMock = $this->getMockBuilder(Notification::class) ->disableOriginalConstructor() @@ -254,7 +266,10 @@ public function testProcessNotificationWithSuccess() $orderHelper = $this->createMock(OrderHelper::class); $orderHelper->method('getOrderByIncrementId')->willReturn($order); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $payment = $this->createMock(Payment::class); + $payment->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $mockWebhookHandlerFactory = $this->createMock(WebhookHandlerFactory::class); $webhookHandlerInterface = $this->createMock(WebhookHandlerInterface::class); $webhookHandlerInterface->method('handleWebhook')->willReturn($order); @@ -300,9 +315,12 @@ public function testProcessNotificationWithAdyenWebhookException() $orderHelper = $this->createMock(OrderHelper::class); $orderHelper->method('getOrderByIncrementId')->willReturn($order); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $payment->expects($this->once()) ->method('setAdditionalInformation') ->with('payment_method', $notification->getPaymentMethod()); + $payment->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $webhookHandlerInterfaceMock = $this->createMock(WebhookHandlerInterface::class); $webhookHandlerInterfaceMock->method('handleWebhook')->willThrowException(new AdyenWebhookException( @@ -316,6 +334,8 @@ public function testProcessNotificationWithAdyenWebhookException() $logger = $this->createMock(AdyenLogger::class); $logger->method('getOrderContext')->with($order); + $paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + $webhookHandler = $this->getMockBuilder(Webhook::class) ->setConstructorArgs([ $this->createMock(Data::class), @@ -326,7 +346,8 @@ public function testProcessNotificationWithAdyenWebhookException() $logger, $webhookHandlerFactory, $orderHelper, - $this->createMock(OrderRepository::class) + $this->createMock(OrderRepository::class), + $paymentMethodsHelperMock ]) ->onlyMethods([ 'updateNotification', @@ -352,7 +373,13 @@ public function testProcessNotificationWithGeneralException() $notification->method('getPspreference')->willReturn('ABCD1234GHJK5678'); $notification->method('getPaymentMethod')->willReturn('ADYEN_CC'); + $paymentMethodInstaceMock = $this->createMock(MethodInterface::class); + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstaceMock); + $payment = $this->createMock(Payment::class); + $payment->method('getMethodInstance')->willReturn($paymentMethodInstaceMock); + $order = $this->createMock(Order::class); $order->method('getState')->willReturn(Order::STATE_NEW); $order->method('getIncrementId')->willReturn(123); @@ -379,6 +406,8 @@ public function testProcessNotificationWithGeneralException() $logger = $this->createMock(AdyenLogger::class); $logger->method('getOrderContext')->with($order); + $paymentMethodHelperMock = $this->createMock(PaymentMethods::class); + $webhookHandler = $this->getMockBuilder(Webhook::class) ->setConstructorArgs([ $this->createMock(Data::class), @@ -389,7 +418,8 @@ public function testProcessNotificationWithGeneralException() $logger, $webhookHandlerFactory, $orderHelper, - $this->createMock(OrderRepository::class) + $this->createMock(OrderRepository::class), + $paymentMethodHelperMock ]) ->onlyMethods([ 'updateNotification', @@ -408,9 +438,15 @@ public function testProcessNotificationWithGeneralException() public function testAddNotificationDetailsHistoryCommentWithFullRefund() { + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); + $orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); + $orderMock->method('getPayment')->willReturn($paymentMock); $notificationMock = $this->getMockBuilder(Notification::class) ->disableOriginalConstructor() ->getMock(); @@ -475,6 +511,10 @@ public function testAddNotificationDetailsHistoryCommentWithPendingEventCode() $adyenHelperMock = $this->createMock(Data::class); $chargedCurrencyMock = $this->createMock(ChargedCurrency::class); + $paymentMethodInstaceMock = $this->createMock(MethodInterface::class); + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstaceMock); + $webhook = $this->createWebhook( $adyenHelperMock, null, @@ -493,6 +533,7 @@ public function testAddNotificationDetailsHistoryCommentWithPendingEventCode() ->willReturn('some_psp_reference'); $orderMock->method('getStoreId') ->willReturn(1); + $orderMock->method('getPayment')->willReturn($paymentMock); $configHelperMock->method('getConfigData') ->with('pending_status', 'adyen_abstract', 1) @@ -559,7 +600,8 @@ protected function createWebhook( $mockLogger = null, $mockWebhookHandlerFactory = null, $mockOrderHelper = null, - $mockOrderRepository = null + $mockOrderRepository = null, + $paymentMethodsHelperMock = null ): Webhook { if (is_null($mockAdyenHelper)) { @@ -589,6 +631,9 @@ protected function createWebhook( if (is_null($mockOrderRepository)) { $mockOrderRepository = $this->createMock(OrderRepository::class); } + if (is_null($paymentMethodsHelperMock)) { + $paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + } return new Webhook( $mockAdyenHelper, $mockSerializer, @@ -598,7 +643,8 @@ protected function createWebhook( $mockLogger, $mockWebhookHandlerFactory, $mockOrderHelper, - $mockOrderRepository + $mockOrderRepository, + $paymentMethodsHelperMock ); } } diff --git a/Test/Unit/Observer/BeforeShipmentObserverTest.php b/Test/Unit/Observer/BeforeShipmentObserverTest.php index ece633c2f3..f9d5b05ae0 100644 --- a/Test/Unit/Observer/BeforeShipmentObserverTest.php +++ b/Test/Unit/Observer/BeforeShipmentObserverTest.php @@ -21,6 +21,7 @@ use Exception; use Magento\Framework\Event; use Magento\Framework\Event\Observer; +use Magento\Payment\Model\MethodInterface; use Magento\Sales\Api\Data\ShipmentItemInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\InvoiceRepository; @@ -117,9 +118,15 @@ public function testNonOpenInvoicePaymentMethod() { $randomPaymentMethod = 'adyen_klarna'; + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $this->paymentMethodsHelperMock->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) + ->willReturn(false); $this->paymentMock->method('getMethod')->willReturn($randomPaymentMethod); + $this->paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $this->paymentMock->method('getAdditionalInformation') ->with(AdyenPaymentMethodDataAssignObserver::BRAND_CODE) ->willReturn('klarna'); @@ -128,10 +135,6 @@ public function testNonOpenInvoicePaymentMethod() ->with('capture_for_openinvoice', BeforeShipmentObserver::XML_ADYEN_ABSTRACT_PREFIX, 1) ->willReturn(BeforeShipmentObserver::ONSHIPMENT_CAPTURE_OPENINVOICE); - $this->adyenHelperMock->method('isPaymentMethodOpenInvoiceMethod') - ->with('klarna') - ->willReturn(false); - $this->adyenLoggerMock->expects($this->once())->method('info'); $this->orderMock->expects($this->never())->method('canInvoice'); @@ -142,9 +145,15 @@ public function testNonInvoicableOrder() { $randomPaymentMethod = 'adyen_klarna'; + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); + $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $this->paymentMethodsHelperMock->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) + ->willReturn(true); $this->paymentMock->method('getMethod')->willReturn($randomPaymentMethod); + $this->paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $this->paymentMock->method('getAdditionalInformation') ->with(AdyenPaymentMethodDataAssignObserver::BRAND_CODE) ->willReturn('klarna'); @@ -153,10 +162,6 @@ public function testNonInvoicableOrder() ->with('capture_for_openinvoice', BeforeShipmentObserver::XML_ADYEN_ABSTRACT_PREFIX, 1) ->willReturn(BeforeShipmentObserver::ONSHIPMENT_CAPTURE_OPENINVOICE); - $this->adyenHelperMock->method('isPaymentMethodOpenInvoiceMethod') - ->with('klarna') - ->willReturn(true); - $this->orderMock->method('canInvoice')->willReturn(false); $this->adyenLoggerMock->expects($this->once())->method('info'); @@ -168,21 +173,23 @@ public function testSuccessfulShipment() { $randomPaymentMethod = 'adyen_klarna'; - $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); $this->paymentMock->method('getMethod')->willReturn($randomPaymentMethod); + $this->paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $this->paymentMock->method('getAdditionalInformation') ->with(AdyenPaymentMethodDataAssignObserver::BRAND_CODE) ->willReturn('klarna'); + $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $this->paymentMethodsHelperMock->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) + ->willReturn(true); + $this->configHelperMock->method('getConfigData') ->with('capture_for_openinvoice', BeforeShipmentObserver::XML_ADYEN_ABSTRACT_PREFIX, 1) ->willReturn(BeforeShipmentObserver::ONSHIPMENT_CAPTURE_OPENINVOICE); - $this->adyenHelperMock->method('isPaymentMethodOpenInvoiceMethod') - ->with('klarna') - ->willReturn(true); - $invoiceMock = $this->createMock(Order\Invoice::class); $invoiceMock->method('getOrder')->willReturn($this->orderMock); @@ -205,9 +212,10 @@ public function testSaveError() $randomPaymentMethod = 'adyen_klarna'; - $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $paymentMethodInstanceMock = $this->createMock(MethodInterface::class); $this->paymentMock->method('getMethod')->willReturn($randomPaymentMethod); + $this->paymentMock->method('getMethodInstance')->willReturn($paymentMethodInstanceMock); $this->paymentMock->method('getAdditionalInformation') ->with(AdyenPaymentMethodDataAssignObserver::BRAND_CODE) ->willReturn('klarna'); @@ -216,8 +224,10 @@ public function testSaveError() ->with('capture_for_openinvoice', BeforeShipmentObserver::XML_ADYEN_ABSTRACT_PREFIX, 1) ->willReturn(BeforeShipmentObserver::ONSHIPMENT_CAPTURE_OPENINVOICE); - $this->adyenHelperMock->method('isPaymentMethodOpenInvoiceMethod') - ->with('klarna') + $this->paymentMethodsHelperMock->method('isAdyenPayment')->willReturn(true); + $this->paymentMethodsHelperMock->expects($this->once()) + ->method('isOpenInvoice') + ->with($paymentMethodInstanceMock) ->willReturn(true); $invoiceMock = $this->createMock(Order\Invoice::class); diff --git a/etc/config.xml b/etc/config.xml index cfc9d8646b..1a5ee9f5ad 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -172,6 +172,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -374,6 +375,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -569,6 +571,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -591,6 +594,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -679,6 +683,7 @@ 0 1 0 + 1 adyen-alternative-payment-method @@ -701,6 +706,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -877,6 +883,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1113,6 +1120,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1135,6 +1143,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1157,6 +1166,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1179,6 +1189,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1323,6 +1334,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1353,6 +1365,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1537,6 +1550,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1612,6 +1626,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1634,6 +1649,7 @@ 1 1 0 + 1 adyen-alternative-payment-method @@ -1730,6 +1746,7 @@ 1 0 0 + 1 adyen-alternative-payment-method @@ -1796,6 +1813,7 @@ 1 1 1 + 1 adyen-alternative-payment-method @@ -1930,6 +1948,7 @@ 1 0 0 + 1 adyen-alternative-payment-method @@ -2086,6 +2105,7 @@ 1 1 0 + 1 adyen-alternative-payment-method From 058315f160f7c8929d73cb1c56ea136f58c48698 Mon Sep 17 00:00:00 2001 From: candemiralp <20255503+candemiralp@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:23:45 +0000 Subject: [PATCH 2/2] chore(release): bump to 9.15.0 --- VERSION | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index ac72f7619e..a4fac8e376 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.14.1 +9.15.0 diff --git a/composer.json b/composer.json index 71625cc86b..91b8f812dc 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "adyen/module-payment", "description": "Official Magento2 Plugin to connect to Payment Service Provider Adyen.", "type": "magento2-module", - "version": "9.14.1", + "version": "9.15.0", "license": "MIT", "repositories": [ {