Skip to content

Commit

Permalink
[ECP-8893] Allow usage of storedPaymentMethodId on headless payment r…
Browse files Browse the repository at this point in the history
…equests (#2431)

* [ECP-8893] Allow usage of storedPaymentMethodId in the request

* [ECP-8893] Write unit tests
  • Loading branch information
candemiralp authored Jan 12, 2024
1 parent 14743b1 commit 2bcff1a
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 8 deletions.
2 changes: 0 additions & 2 deletions Gateway/Request/RecurringDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ public function build(array $buildSubject): array
$body = $this->adyenRequestsHelper->buildCardRecurringData($storeId, $payment);
} elseif ($this->paymentMethodsHelper->isAlternativePaymentMethod($method)) {
$body = $this->vaultHelper->buildPaymentMethodRecurringData($payment, $storeId);
} elseif ($method === PaymentMethods::ADYEN_ONE_CLICK) {
$body = $this->adyenRequestsHelper->buildAdyenTokenizedPaymentRecurringData($storeId, $payment);
} elseif ($method !== PaymentMethods::ADYEN_PAY_BY_LINK) {
$this->adyenLogger->addAdyenWarning(
sprintf('Unknown payment method: %s', $payment->getMethod()),
Expand Down
16 changes: 13 additions & 3 deletions Gateway/Request/ShopperInteractionDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Adyen\Payment\Gateway\Request;

use Adyen\Payment\Helper\StateData;
use Adyen\Payment\Model\Ui\Adminhtml\AdyenMotoConfigProvider;
use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider;
use Magento\Framework\App\State;
Expand All @@ -26,10 +27,14 @@ class ShopperInteractionDataBuilder implements BuilderInterface
const SHOPPER_INTERACTION_ECOMMERCE = 'Ecommerce';

private State $appState;
private StateData $stateData;

public function __construct(Context $context)
{
public function __construct(
Context $context,
StateData $stateData
) {
$this->appState = $context->getAppState();
$this->stateData = $stateData;
}

/**
Expand All @@ -42,6 +47,7 @@ public function build(array $buildSubject)
/** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */
$paymentDataObject = SubjectReader::readPayment($buildSubject);
$payment = $paymentDataObject->getPayment();
$order = $payment->getOrder();
$paymentMethod = $payment->getMethodInstance()->getCode();

if ($paymentMethod == AdyenPayByLinkConfigProvider::CODE) {
Expand All @@ -52,11 +58,15 @@ public function build(array $buildSubject)
// Ecommerce is the default shopperInteraction
$shopperInteraction = self::SHOPPER_INTERACTION_ECOMMERCE;

// Check if it's a tokenised payment or not.
$stateData = $this->stateData->getStateData($order->getQuoteId());
$storedPaymentMethodId = $this->stateData->getStoredPaymentMethodIdFromStateData($stateData);

if ($paymentMethod == AdyenMotoConfigProvider::CODE &&
$this->appState->getAreaCode() == \Magento\Framework\App\Area::AREA_ADMINHTML) {
// Backend CC orders are MOTO
$shopperInteraction = self::SHOPPER_INTERACTION_MOTO;
} elseif (str_contains($paymentMethod, '_vault')) {
} elseif (str_contains($paymentMethod, '_vault') || isset($storedPaymentMethodId)) {
// Vault is ContAuth
$shopperInteraction = self::SHOPPER_INTERACTION_CONTAUTH;
}
Expand Down
4 changes: 3 additions & 1 deletion Helper/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ public function buildCardRecurringData(int $storeId, $payment): array
$request['storePaymentMethod'] = $storePaymentMethod;
}

if ($storePaymentMethod) {
$storedPaymentMethodId = $this->stateData->getStoredPaymentMethodIdFromStateData($stateData);

if ($storePaymentMethod || isset($storedPaymentMethodId)) {
$recurringProcessingModel = $payment->getAdditionalInformation('recurringProcessingModel');

if (isset($recurringProcessingModel)) {
Expand Down
5 changes: 5 additions & 0 deletions Helper/StateData.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,9 @@ public function removeStateData(int $stateDataId, ?int $quoteId = null): bool
return true;
}
}

public function getStoredPaymentMethodIdFromStateData(array $stateData): ?string
{
return $stateData['paymentMethod']['storedPaymentMethodId'] ?? null;
}
}
13 changes: 11 additions & 2 deletions Helper/Vault.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,24 @@ class Vault
private PaymentTokenRepositoryInterface $paymentTokenRepository;
private Config $config;
private PaymentMethods $paymentMethodsHelper;
private StateData $stateData;

public function __construct(
AdyenLogger $adyenLogger,
PaymentTokenManagement $paymentTokenManagement,
PaymentTokenFactoryInterface $paymentTokenFactory,
PaymentTokenRepositoryInterface $paymentTokenRepository,
Config $config,
PaymentMethods $paymentMethodsHelper
PaymentMethods $paymentMethodsHelper,
StateData $stateData
) {
$this->adyenLogger = $adyenLogger;
$this->paymentTokenManagement = $paymentTokenManagement;
$this->paymentTokenFactory = $paymentTokenFactory;
$this->paymentTokenRepository = $paymentTokenRepository;
$this->config = $config;
$this->paymentMethodsHelper = $paymentMethodsHelper;
$this->stateData = $stateData;
}

/**
Expand Down Expand Up @@ -144,11 +147,17 @@ public function buildPaymentMethodRecurringData(InfoInterface $payment, int $sto
$requestRpm = $payment->getAdditionalInformation('recurringProcessingModel');
$configuredRpm = $this->getPaymentMethodRecurringProcessingModel($paymentMethod->getCode(), $storeId);

$stateData = $this->stateData->getStateData($payment->getOrder()->getQuoteId());
$storedPaymentMethodId = $this->stateData->getStoredPaymentMethodIdFromStateData($stateData);

$recurringProcessingModel = $requestRpm ?? $configuredRpm;

if (isset($recurringProcessingModel)) {
$request['storePaymentMethod'] = true;
$request['recurringProcessingModel'] = $recurringProcessingModel;

if (is_null($storedPaymentMethodId)) {
$request['storePaymentMethod'] = true;
}
}

return $request;
Expand Down
123 changes: 123 additions & 0 deletions Test/Unit/Gateway/Request/ShopperInteractionDataBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php
/**
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/

namespace Adyen\Payment\Test\Gateway\Request;

use Adyen\Payment\Gateway\Request\ShopperInteractionDataBuilder;
use Adyen\Payment\Helper\StateData;
use Adyen\Payment\Model\Method\Adapter;
use Adyen\Payment\Model\Ui\Adminhtml\AdyenMotoConfigProvider;
use Adyen\Payment\Model\Ui\AdyenCcConfigProvider;
use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider;
use Adyen\Payment\Test\Unit\AbstractAdyenTestCase;
use Magento\Framework\App\Area;
use Magento\Framework\App\State;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Payment\Gateway\Data\PaymentDataObject;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Payment;

class ShopperInteractionDataBuilderTest extends AbstractAdyenTestCase
{
private $shopperInteractionDataBuilder;
private $appState;
private $stateData;

protected function setUp(): void
{
$this->objectManager = new ObjectManager($this);

$this->appState = $this->createMock(State::class);
$this->stateData = $this->createPartialMock(StateData::class, [
'getStateData'
]);

$this->shopperInteractionDataBuilder = $this->objectManager->getObject(
ShopperInteractionDataBuilder::class, [
'appState' => $this->appState,
'stateData' => $this->stateData
]
);
}

public function testPayByLinkRequest()
{
$buildSubject = [
'payment' => $this->createConfiguredMock(PaymentDataObject::class, [
'getPayment' => $this->createConfiguredMock(Payment::class, [
'getMethodInstance' => $this->createConfiguredMock(Adapter::class, [
'getCode' => AdyenPayByLinkConfigProvider::CODE
])
])
])
];

$this->assertEmpty($this->shopperInteractionDataBuilder->build($buildSubject));
}

public function testMotoRequest()
{
$buildSubject = [
'payment' => $this->createConfiguredMock(PaymentDataObject::class, [
'getPayment' => $this->createConfiguredMock(Payment::class, [
'getMethodInstance' => $this->createConfiguredMock(Adapter::class, [
'getCode' => AdyenMotoConfigProvider::CODE
]),
'getOrder' => $this->createConfiguredMock(Order::class, [
'getQuoteId' => 1
])
])
])
];

$this->appState->method('getAreaCode')->willReturn(Area::AREA_ADMINHTML);
$this->stateData->method('getStateData')->willReturn([]);

$request = $this->shopperInteractionDataBuilder->build($buildSubject);

$this->assertArrayHasKey('shopperInteraction', $request['body']);
$this->assertEquals(
ShopperInteractionDataBuilder::SHOPPER_INTERACTION_MOTO,
$request['body']['shopperInteraction']
);
}

public function testRecurringRequest()
{
$buildSubject = [
'payment' => $this->createConfiguredMock(PaymentDataObject::class, [
'getPayment' => $this->createConfiguredMock(Payment::class, [
'getMethodInstance' => $this->createConfiguredMock(Adapter::class, [
'getCode' => AdyenCcConfigProvider::CODE
]),
'getOrder' => $this->createConfiguredMock(Order::class, [
'getQuoteId' => 1
])
])
])
];

$this->appState->method('getAreaCode')->willReturn(Area::AREA_ADMINHTML);
$this->stateData->method('getStateData')->willReturn([
'paymentMethod' => [
'storedPaymentMethodId' => hash('md5', time())
]
]);

$request = $this->shopperInteractionDataBuilder->build($buildSubject);

$this->assertArrayHasKey('shopperInteraction', $request['body']);
$this->assertEquals(
ShopperInteractionDataBuilder::SHOPPER_INTERACTION_CONTAUTH,
$request['body']['shopperInteraction']
);
}
}
43 changes: 43 additions & 0 deletions Test/Unit/Helper/StateDataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,47 @@ public function testRemoveStateDataException()

$this->stateDataHelper->removeStateData($stateDataId, $quoteId);
}

/**
* @dataProvider storedPaymentMethodIdProvider
*/
public function testGetStoredPaymentMethodId($stateData, $expectedResult)
{
$this->assertEquals(
$expectedResult,
$this->stateDataHelper->getStoredPaymentMethodIdFromStateData($stateData)
);
}

public static function storedPaymentMethodIdProvider(): array
{
$mockStoredPaymentMethodId = hash('md5', time());

return [
[
'stateData' => [
'paymentMethod' => [
'storedPaymentMethodId' => $mockStoredPaymentMethodId
]
],
'expectedResult' => $mockStoredPaymentMethodId
],
[
'stateData' => [
'paymentMethod' => [
'storedPaymentMethodId' => null
]
],
'expectedResult' => null
],
[
'stateData' => [
'paymentMethod' => [
'type' => 'scheme'
]
],
'expectedResult' => null
]
];
}
}
Loading

0 comments on commit 2bcff1a

Please sign in to comment.