diff --git a/Gateway/Request/CheckoutDataBuilder.php b/Gateway/Request/CheckoutDataBuilder.php index 3c53d55ca..ef7c67225 100644 --- a/Gateway/Request/CheckoutDataBuilder.php +++ b/Gateway/Request/CheckoutDataBuilder.php @@ -16,6 +16,7 @@ use Adyen\Payment\Helper\Data; 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; @@ -209,7 +210,8 @@ public function build(array $buildSubject): array unset($requestBody['installments']); } - $requestBody['additionalData']['allow3DS2'] = true; + $requestBody['additionalData']['allow3DS2'] = + $this->configHelper->getThreeDSFlow($storeId) === ThreeDSFlow::THREEDS_NATIVE; return [ 'body' => $requestBody diff --git a/Gateway/Request/RecurringVaultDataBuilder.php b/Gateway/Request/RecurringVaultDataBuilder.php index 1e9a32426..4cac64a81 100644 --- a/Gateway/Request/RecurringVaultDataBuilder.php +++ b/Gateway/Request/RecurringVaultDataBuilder.php @@ -11,8 +11,10 @@ namespace Adyen\Payment\Gateway\Request; +use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\StateData; use Adyen\Payment\Helper\Vault; +use Adyen\Payment\Model\Config\Source\ThreeDSFlow; use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; @@ -22,13 +24,16 @@ class RecurringVaultDataBuilder implements BuilderInterface { private StateData $stateData; private Vault $vaultHelper; + private Config $configHelper; public function __construct( StateData $stateData, - Vault $vaultHelper + Vault $vaultHelper, + Config $configHelper ) { $this->stateData = $stateData; $this->vaultHelper = $vaultHelper; + $this->configHelper = $configHelper; } public function build(array $buildSubject): array @@ -62,7 +67,8 @@ public function build(array $buildSubject): array * Due to new VISA compliance requirements, holderName is added to the payments call */ if ($paymentMethod->getCode() === AdyenCcConfigProvider::CC_VAULT_CODE) { - $requestBody['additionalData']['allow3DS2'] = true; + $requestBody['additionalData']['allow3DS2'] = + $this->configHelper->getThreeDSFlow($order->getStoreId()) === ThreeDSFlow::THREEDS_NATIVE; $requestBody['paymentMethod']['holderName'] = $details['cardHolderName'] ?? null; } diff --git a/Helper/Config.php b/Helper/Config.php index 6fafd8814..8657bfe26 100644 --- a/Helper/Config.php +++ b/Helper/Config.php @@ -56,6 +56,7 @@ class Config const AUTO_CAPTURE_OPENINVOICE = 'auto'; const XML_RECURRING_CONFIGURATION = 'recurring_configuration'; const XML_ALLOW_MULTISTORE_TOKENS = 'allow_multistore_tokens'; + const XML_THREEDS_FLOW = 'threeds_flow'; protected ScopeConfigInterface $scopeConfig; private EncryptorInterface $encryptor; @@ -576,6 +577,21 @@ public function getAllowMultistoreTokens(int $storeId = null): ?bool ); } + /** + * Returns the preferred ThreeDS authentication type for card and card vault payments. + * + * @param int|null $storeId + * @return string + */ + public function getThreeDSFlow(int $storeId = null): string + { + return $this->getConfigData( + self::XML_THREEDS_FLOW, + self::XML_ADYEN_CC, + $storeId + ); + } + public function getConfigData(string $field, string $xmlPrefix, ?int $storeId, bool $flag = false): mixed { $path = implode("/", [self::XML_PAYMENT_PREFIX, $xmlPrefix, $field]); diff --git a/Model/Config/Source/ThreeDSFlow.php b/Model/Config/Source/ThreeDSFlow.php new file mode 100755 index 000000000..d2bebd614 --- /dev/null +++ b/Model/Config/Source/ThreeDSFlow.php @@ -0,0 +1,31 @@ + + */ + +namespace Adyen\Payment\Model\Config\Source; + +use Magento\Framework\Data\OptionSourceInterface; + +class ThreeDSFlow implements OptionSourceInterface +{ + const THREEDS_NATIVE = 'native'; + const THREEDS_REDIRECT = 'redirect'; + + /** + * @return array + */ + public function toOptionArray(): array + { + return [ + ['value' => self::THREEDS_NATIVE, 'label' => __('Native 3D Secure 2')], + ['value' => self::THREEDS_REDIRECT, 'label' => __('Redirect 3D Secure 2')], + ]; + } +} diff --git a/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php b/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php new file mode 100644 index 000000000..09de31742 --- /dev/null +++ b/Test/Unit/Gateway/Request/CheckoutDataBuilderTest.php @@ -0,0 +1,83 @@ +adyenHelperMock = $this->createMock(Data::class); + $this->stateDataMock = $this->createMock(StateData::class); + $this->cartRepositoryMock = $this->createMock(CartRepositoryInterface::class); + $this->chargedCurrencyMock = $this->createMock(ChargedCurrency::class); + $this->configMock = $this->createMock(Config::class); + $this->openInvoiceMock = $this->createMock(OpenInvoice::class); + + $this->checkoutDataBuilder = new CheckoutDataBuilder( + $this->adyenHelperMock, + $this->stateDataMock, + $this->cartRepositoryMock, + $this->chargedCurrencyMock, + $this->configMock, + $this->openInvoiceMock + ); + + parent::setUp(); + } + + public function tearDown(): void + { + $this->checkoutDataBuilder = null; + } + + + public function testAllowThreeDSFlag() + { + $storeId = 1; + + $orderMock = $this->createMock(Order::class); + $orderMock->method('getQuoteId')->willReturn(1); + $orderMock->method('getStoreId')->willReturn($storeId); + + $paymentMock = $this->createMock(Payment::class); + $paymentMock->method('getOrder')->willReturn($orderMock); + + $buildSubject = [ + 'payment' => $this->createConfiguredMock(PaymentDataObject::class, [ + 'getPayment' => $paymentMock + ]) + ]; + + $this->configMock->expects($this->once()) + ->method('getThreeDSFlow') + ->with($storeId) + ->willReturn(ThreeDSFlow::THREEDS_NATIVE); + + $request = $this->checkoutDataBuilder->build($buildSubject); + + $this->assertTrue($request['body']['additionalData']['allow3DS2']); + } +} diff --git a/Test/Unit/Helper/ConfigTest.php b/Test/Unit/Helper/ConfigTest.php index 5e43d3b10..bf70dd4f4 100644 --- a/Test/Unit/Helper/ConfigTest.php +++ b/Test/Unit/Helper/ConfigTest.php @@ -128,4 +128,24 @@ public function testRemoveConfigData() $this->configHelper->removeConfigData($field, $xml_prefix); } + + public function testGetThreeDSModes() + { + $storeId = PHP_INT_MAX; + $expectedResult = MethodInterface::ACTION_ORDER; + $path = sprintf( + "%s/%s/%s", + Config::XML_PAYMENT_PREFIX, + Config::XML_ADYEN_CC, + Config::XML_THREEDS_FLOW + ); + + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with($this->equalTo($path), $this->equalTo(ScopeInterface::SCOPE_STORE), $this->equalTo($storeId)) + ->willReturn($expectedResult); + + $result = $this->configHelper->getThreeDSFlow($storeId); + $this->assertEquals($expectedResult, $result); + } } diff --git a/Test/Unit/Model/Config/Source/ThreeDSModeTest.php b/Test/Unit/Model/Config/Source/ThreeDSModeTest.php new file mode 100644 index 000000000..e7ff61dfe --- /dev/null +++ b/Test/Unit/Model/Config/Source/ThreeDSModeTest.php @@ -0,0 +1,21 @@ + ThreeDSFlow::THREEDS_NATIVE, 'label' => __('Native 3D Secure 2')], + ['value' => ThreeDSFlow::THREEDS_REDIRECT, 'label' => __('Redirect 3D Secure 2')], + ]; + + $this->assertEquals($expected, $threeDSModeSource->toOptionArray()); + } +} diff --git a/etc/adminhtml/system/adyen_card_payments.xml b/etc/adminhtml/system/adyen_card_payments.xml index dac3c2624..b356ad22a 100755 --- a/etc/adminhtml/system/adyen_card_payments.xml +++ b/etc/adminhtml/system/adyen_card_payments.xml @@ -74,5 +74,10 @@ Magento\Config\Model\Config\Source\Yesno payment/adyen_cc/enable_click_to_pay + + + Adyen\Payment\Model\Config\Source\ThreeDSFlow + payment/adyen_cc/threeds_flow + diff --git a/etc/config.xml b/etc/config.xml index dd7997bbd..d61cff0b2 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -61,6 +61,7 @@ 1 1 1 + native adyen