diff --git a/Api/AdyenPaymentMethodManagementInterface.php b/Api/AdyenPaymentMethodManagementInterface.php index ab8d09fd1..2423ec120 100644 --- a/Api/AdyenPaymentMethodManagementInterface.php +++ b/Api/AdyenPaymentMethodManagementInterface.php @@ -22,7 +22,13 @@ interface AdyenPaymentMethodManagementInterface * @param string $cartId * @param string|null $shopperLocale * @param string|null $country + * @param string|null $channel * @return string */ - public function getPaymentMethods(string $cartId, ?string $shopperLocale = null, ?string $country = null) :string; + public function getPaymentMethods( + string $cartId, + ?string $shopperLocale = null, + ?string $country = null, + ?string $channel = null + ) :string; } diff --git a/Api/GuestAdyenPaymentMethodManagementInterface.php b/Api/GuestAdyenPaymentMethodManagementInterface.php index c2d3d4ba3..2f6d284c7 100644 --- a/Api/GuestAdyenPaymentMethodManagementInterface.php +++ b/Api/GuestAdyenPaymentMethodManagementInterface.php @@ -22,7 +22,13 @@ interface GuestAdyenPaymentMethodManagementInterface * @param string $cartId * @param string|null $shopperLocale * @param string|null $country + * @param string|null $channel * @return string */ - public function getPaymentMethods(string $cartId, ?string $shopperLocale = null, ?string $country = null): string; + public function getPaymentMethods( + string $cartId, + ?string $shopperLocale = null, + ?string $country = null, + ?string $channel = null + ): string; } diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php index 8e5d42387..f8972d18c 100644 --- a/Helper/PaymentMethods.php +++ b/Helper/PaymentMethods.php @@ -66,6 +66,8 @@ class PaymentMethods extends AbstractHelper const ADYEN_GROUP_ALTERNATIVE_PAYMENT_METHODS = 'adyen-alternative-payment-method'; + const VALID_CHANNELS = ["iOS", "Android", "Web"]; + /* * Following payment methods should be enabled with their own configuration path. */ @@ -229,12 +231,18 @@ public function __construct( * @param int $quoteId * @param string|null $country * @param string|null $shopperLocale + * @param string|null $channel * @return string * @throws AdyenException * @throws LocalizedException * @throws NoSuchEntityException */ - public function getPaymentMethods(int $quoteId, ?string $country = null, ?string $shopperLocale = null): string + public function getPaymentMethods( + int $quoteId, + ?string $country = null, + ?string $shopperLocale = null, + ?string $channel = null + ): string { // get quote from quoteId $quote = $this->quoteRepository->getActive($quoteId); @@ -245,7 +253,7 @@ public function getPaymentMethods(int $quoteId, ?string $country = null, ?string $this->setQuote($quote); - return $this->fetchPaymentMethods($country, $shopperLocale); + return $this->fetchPaymentMethods($country, $shopperLocale, $channel); } /** @@ -327,12 +335,17 @@ public function removePaymentMethodsActivation(string $scope, int $scopeId): voi /** * @param string|null $country * @param string|null $shopperLocale + * @param string|null $channel * @return string * @throws AdyenException * @throws LocalizedException * @throws NoSuchEntityException */ - protected function fetchPaymentMethods(?string $country = null, ?string $shopperLocale = null): string + protected function fetchPaymentMethods( + ?string $country = null, + ?string $shopperLocale = null, + ?string $channel = null + ): string { $quote = $this->getQuote(); $store = $quote->getStore(); @@ -342,7 +355,14 @@ protected function fetchPaymentMethods(?string $country = null, ?string $shopper return json_encode([]); } - $requestData = $this->getPaymentMethodsRequest($merchantAccount, $store, $quote, $shopperLocale, $country); + $requestData = $this->getPaymentMethodsRequest( + $merchantAccount, + $store, + $quote, + $shopperLocale, + $country, + $channel + ); $responseData = $this->getPaymentMethodsResponse($requestData, $store); if (empty($responseData['paymentMethods'])) { return json_encode([]); @@ -526,6 +546,7 @@ protected function getCurrentShopperReference(): ?string * @param Quote $quote * @param string|null $shopperLocale * @param string|null $country + * @param string|null $channel * @return array * @throws AdyenException */ @@ -534,15 +555,18 @@ protected function getPaymentMethodsRequest( Store $store, Quote $quote, ?string $shopperLocale = null, - ?string $country = null + ?string $country = null, + ?string $channel = null ): array { $currencyCode = $this->chargedCurrency->getQuoteAmountCurrency($quote)->getCurrencyCode(); + $channel = in_array($channel, self::VALID_CHANNELS, true) ? $channel : "Web"; + $paymentMethodRequest = [ - "channel" => "Web", + "channel" => $channel ?? "Web", "merchantAccount" => $merchantAccount, "countryCode" => $country ?? $this->getCurrentCountryCode($store), - "shopperLocale" => $shopperLocale ?: $this->adyenHelper->getCurrentLocaleCode($store->getId()), + "shopperLocale" => $shopperLocale ?? $this->adyenHelper->getCurrentLocaleCode($store->getId()), "amount" => [ "currency" => $currencyCode ] diff --git a/Helper/PaymentMethodsFilter.php b/Helper/PaymentMethodsFilter.php index 2798b122b..95380553c 100644 --- a/Helper/PaymentMethodsFilter.php +++ b/Helper/PaymentMethodsFilter.php @@ -15,6 +15,7 @@ use Adyen\Payment\Model\Ui\AdyenPosCloudConfigProvider; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\Data\CartInterface; +use Magento\Framework\App\RequestInterface; class PaymentMethodsFilter { @@ -31,18 +32,24 @@ class PaymentMethodsFilter ]; private PaymentMethods $paymentMethods; + private RequestInterface $request; public function __construct( - PaymentMethods $paymentMethods + PaymentMethods $paymentMethods, + RequestInterface $request ) { $this->paymentMethods = $paymentMethods; + $this->request = $request; } public function sortAndFilterPaymentMethods(array $magentoPaymentMethods, CartInterface $quote): array { + $channel = $this->request->getParam('channel'); $adyenPaymentMethodsResponse = $this->paymentMethods->getPaymentMethods( $quote->getId(), - $quote->getBillingAddress()->getCountryId() + $quote->getBillingAddress()->getCountryId(), + null, + $channel ); $adyenPaymentMethodsDecoded = json_decode($adyenPaymentMethodsResponse, true); diff --git a/Model/Api/AdyenPaymentMethodManagement.php b/Model/Api/AdyenPaymentMethodManagement.php index a97234bd8..069ad7a44 100644 --- a/Model/Api/AdyenPaymentMethodManagement.php +++ b/Model/Api/AdyenPaymentMethodManagement.php @@ -24,8 +24,13 @@ public function __construct( $this->paymentMethodsHelper = $paymentMethodsHelper; } - public function getPaymentMethods(string $cartId, ?string $shopperLocale = null, ?string $country = null) : string + public function getPaymentMethods( + string $cartId, + ?string $shopperLocale = null, + ?string $country = null, + ?string $channel = null + ) : string { - return $this->paymentMethodsHelper->getPaymentMethods($cartId, $country, $shopperLocale); + return $this->paymentMethodsHelper->getPaymentMethods($cartId, $country, $shopperLocale, $channel); } } diff --git a/Model/Api/GuestAdyenPaymentMethodManagement.php b/Model/Api/GuestAdyenPaymentMethodManagement.php index 5c9d40227..e32d82724 100644 --- a/Model/Api/GuestAdyenPaymentMethodManagement.php +++ b/Model/Api/GuestAdyenPaymentMethodManagement.php @@ -28,10 +28,15 @@ public function __construct( $this->paymentMethodsHelper = $paymentMethodsHelper; } - public function getPaymentMethods(string $cartId, ?string $shopperLocale = null, ?string $country = null): string { + public function getPaymentMethods( + string $cartId, + ?string $shopperLocale = null, + ?string $country = null, + ?string $channel = null + ): string { $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id'); $quoteId = $quoteIdMask->getQuoteId(); - return $this->paymentMethodsHelper->getPaymentMethods($quoteId, $country, $shopperLocale); + return $this->paymentMethodsHelper->getPaymentMethods($quoteId, $country, $shopperLocale, $channel); } } diff --git a/Model/Resolver/GetAdyenPaymentMethods.php b/Model/Resolver/GetAdyenPaymentMethods.php index 5dfb23844..eebcb8422 100644 --- a/Model/Resolver/GetAdyenPaymentMethods.php +++ b/Model/Resolver/GetAdyenPaymentMethods.php @@ -58,6 +58,7 @@ public function resolve( $maskedCartId = $args['cart_id']; $shopperLocale = $args['shopper_locale'] ?? null; $country = $args['country'] ?? null; + $channel = $args['channel'] ?? null; $currentUserId = $context->getUserId(); $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); @@ -68,7 +69,8 @@ public function resolve( $adyenPaymentMethodsResponse = $this->paymentMethodsHelper->getPaymentMethods( intval($cart->getId()), $country, - $shopperLocale + $shopperLocale, + $channel ); return $adyenPaymentMethodsResponse ? $this->preparePaymentMethodGraphQlResponse($adyenPaymentMethodsResponse) : []; diff --git a/Test/Unit/Helper/PaymentMethodsFilterTest.php b/Test/Unit/Helper/PaymentMethodsFilterTest.php index e0ad7ab29..5a862ca02 100644 --- a/Test/Unit/Helper/PaymentMethodsFilterTest.php +++ b/Test/Unit/Helper/PaymentMethodsFilterTest.php @@ -18,6 +18,7 @@ use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\Data\CartInterface; use Magento\Sales\Model\Order\Address; +use Magento\Framework\App\RequestInterface; class PaymentMethodsFilterTest extends AbstractAdyenTestCase { @@ -175,17 +176,20 @@ public function testSortAndFilterPaymentMethods(): void ]) ]); - $cartRepositoryInterfaceMock = $this->createConfiguredMock(CartRepositoryInterface::class, [ - 'get' => $quoteMock - ]); + $channel = 'iOS'; $paymentMethodsHelperMock = $this->createConfiguredMock(PaymentMethods::class, [ 'getPaymentMethods' => self::PAYMENT_METHODS_RESPONSE ]); + $RequestInterfaceMock = $this->getMockBuilder(RequestInterface::class) + ->getMockForAbstractClass(); + + $RequestInterfaceMock->method('getParam')->with('channel')->willReturn($channel); + $paymentMethodsFilterHelper = $this->createPaymentMethodsFilterHelper( $paymentMethodsHelperMock, - $cartRepositoryInterfaceMock + $RequestInterfaceMock ); $sortedMagentoPaymentMethods = @@ -201,16 +205,16 @@ public function testSortAndFilterPaymentMethods(): void protected function createPaymentMethodsFilterHelper( $paymentMethodsHelperMock = null, - $cartRepositoryInterfaceMock = null + $RequestInterfaceMock = null ): PaymentMethodsFilter { if (is_null($paymentMethodsHelperMock)) { $paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); } - if (is_null($cartRepositoryInterfaceMock)) { - $cartRepositoryInterfaceMock = $this->createMock(CartRepositoryInterface::class); + if (is_null($RequestInterfaceMock)) { + $RequestInterfaceMock = $this->createMock(RequestInterface::class); } - return new PaymentMethodsFilter($paymentMethodsHelperMock, $cartRepositoryInterfaceMock); + return new PaymentMethodsFilter($paymentMethodsHelperMock, $RequestInterfaceMock); } } diff --git a/Test/Unit/Model/Api/AdyenPaymentMethodManagementTest.php b/Test/Unit/Model/Api/AdyenPaymentMethodManagementTest.php new file mode 100644 index 000000000..339409b9b --- /dev/null +++ b/Test/Unit/Model/Api/AdyenPaymentMethodManagementTest.php @@ -0,0 +1,48 @@ +paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + + $this->adyenPaymentMethodManagement = new AdyenPaymentMethodManagement( + $this->paymentMethodsHelperMock + ); + } + + public function testGetPaymentMethods(): void + { + $cartId = '12345'; + $shopperLocale = 'en_US'; + $country = 'US'; + $channel = 'web'; + + // Define the expected result + $expectedResult = '["payment_method_1", "payment_method_2"]'; + + $this->paymentMethodsHelperMock->expects($this->once()) + ->method('getPaymentMethods') + ->with($cartId, $country, $shopperLocale, $channel) + ->willReturn($expectedResult); + + $result = $this->adyenPaymentMethodManagement->getPaymentMethods($cartId, $shopperLocale, $country, $channel); + + // Assert that the result matches the expected result + $this->assertEquals($expectedResult, $result); + } +} diff --git a/Test/Unit/Model/Api/GuestAdyenPaymentMethodManagementTest.php b/Test/Unit/Model/Api/GuestAdyenPaymentMethodManagementTest.php new file mode 100644 index 000000000..8de2828bb --- /dev/null +++ b/Test/Unit/Model/Api/GuestAdyenPaymentMethodManagementTest.php @@ -0,0 +1,56 @@ +quoteIdMaskFactoryMock = $this->createGeneratedMock(QuoteIdMaskFactory::class, ['create']); + $this->paymentMethodsHelperMock = $this->createMock(PaymentMethods::class); + + $this->guestAdyenPaymentMethodManagement = new GuestAdyenPaymentMethodManagement( + $this->quoteIdMaskFactoryMock, + $this->paymentMethodsHelperMock + ); + } + + public function testGetPaymentMethods() + { + $cartId = '123'; + $quoteId = 123; + $shopperLocale = 'en_US'; + $country = 'US'; + $channel = 'Web'; + $expectedPaymentMethods = 'sample_payment_methods'; + + $quoteIdMaskMock = $this->createGeneratedMock(QuoteIdMask::class, ['load', 'getQuoteId']); + $quoteIdMaskMock->method('load')->willReturn($quoteIdMaskMock); + $quoteIdMaskMock->method('getQuoteId')->willReturn($quoteId); + $this->quoteIdMaskFactoryMock->method('create')->willReturn($quoteIdMaskMock); + + $this->paymentMethodsHelperMock->expects($this->once()) + ->method('getPaymentMethods') + ->with($quoteId, $country, $shopperLocale, $channel) + ->willReturn($expectedPaymentMethods); + + $result = $this->guestAdyenPaymentMethodManagement->getPaymentMethods($cartId, $shopperLocale, $country, $channel); + + $this->assertEquals($expectedPaymentMethods, $result); + } +} diff --git a/Test/api-functional/GraphQl/AdyenTest.php b/Test/api-functional/GraphQl/AdyenTest.php index e36ec02c9..676a6b834 100644 --- a/Test/api-functional/GraphQl/AdyenTest.php +++ b/Test/api-functional/GraphQl/AdyenTest.php @@ -350,4 +350,38 @@ private function getHeaderMap(string $username = 'customer@example.com', string $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); return ['Authorization' => 'Bearer ' . $customerToken]; } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testAdyenPaymentMethodsWithChannelAndroid() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = <<graphQlQuery($query); + + $this->assertArrayHasKey('adyenPaymentMethods', $response); + $this->assertArrayHasKey('paymentMethodsResponse', $response['adyenPaymentMethods']); + $paymentMethods = $response['adyenPaymentMethods']['paymentMethodsResponse']['paymentMethods']; + + foreach ($paymentMethods as $paymentMethod) { + $this->assertNotEquals('Apple Pay', $paymentMethod['name'], 'Apple Pay should not be listed for channel Android'); + } + } + } diff --git a/Test/api-functional/Webapi/ChannelParameterTest.php b/Test/api-functional/Webapi/ChannelParameterTest.php new file mode 100644 index 000000000..654c9f9e1 --- /dev/null +++ b/Test/api-functional/Webapi/ChannelParameterTest.php @@ -0,0 +1,107 @@ +getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + public function testApplePayExcludedForAndroidChannel() + { + $this->maskedQuoteId = $this->createGuestCart(); + + $this->addItemToCart($this->maskedQuoteId); + $this->setBillingAddress($this->maskedQuoteId); + $this->setShippingInformationWithChannel($this->maskedQuoteId); + + // Verify that Apple Pay is not present for the Android channel + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => "/V1/guest-carts/{$this->maskedQuoteId}/payment-methods", + 'httpMethod' => Request::HTTP_METHOD_GET + ] + ]; + + $response = $this->_webApiCall($serviceInfo); + $this->assertIsArray($response, 'Response should be an array'); + + foreach ($response as $paymentMethod) { + $this->assertArrayHasKey('code', $paymentMethod, 'Each payment method should have a code'); + $this->assertNotEquals('adyen_apple_pay', $paymentMethod['code'], 'Apple Pay should not be present for Android channel'); + } + } + + private function createGuestCart(): string + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => "/guest-carts", + 'httpMethod' => Request::HTTP_METHOD_POST + ] + ]; + + return $this->_webApiCall($serviceInfo, [], null, 'V1'); + } + + private function addItemToCart(string $maskedQuoteId): array + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => "/guest-carts/{$maskedQuoteId}/items", + 'httpMethod' => Request::HTTP_METHOD_POST + ] + ]; + + $payload = '{"cartItem":{"qty":1,"sku":"24-MB04"}}'; + return $this->_webApiCall($serviceInfo, json_decode($payload, true), null, 'V1'); + } + + private function setShippingInformationWithChannel($maskedQuoteId): array + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => "/guest-carts/{$maskedQuoteId}/shipping-information?channel=Android", + 'httpMethod' => Request::HTTP_METHOD_POST + ] + ]; + + $payload = '{"addressInformation":{"shipping_address":{"firstname":"Veronica","lastname":"Costello","company":"Adyen","street":["Simon Carmiggeltstraat","Main Street"],"city":"Amsterdam","region":"Amsterdam","postcode":"1011 DK","country_id":"NL","telephone":"123456789"},"shipping_carrier_code":"flatrate","shipping_method_code":"flatrate"}}'; + return $this->_webApiCall($serviceInfo, json_decode($payload, true), null, 'V1'); + } + + private function setBillingAddress($maskedQuoteId): int + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => "/guest-carts/{$maskedQuoteId}/billing-address", + 'httpMethod' => Request::HTTP_METHOD_POST + ] + ]; + + $payload = '{"address":{"firstname":"Veronica ","lastname":"Costello","company":"Adyen","street":["Simon Carmiggeltstraat","Main Street"],"city":"Amsterdam","region":"Amsterdam","postcode":"1011 DK","country_id":"NL","telephone":"123456789","email":"roni_cost@example.com"},"useForShipping":true}'; + return $this->_webApiCall($serviceInfo, json_decode($payload, true), null, 'V1'); + } +} diff --git a/etc/schema.graphqls b/etc/schema.graphqls index 0a4d824e4..410d7cc86 100644 --- a/etc/schema.graphqls +++ b/etc/schema.graphqls @@ -8,6 +8,7 @@ type Query { cart_id: String! @doc(description: "Cart ID.") shopper_locale: String @doc(description: "Language and country code combination separated by underscore (_), e.g. en_US") country: String @doc(description: "Country code to be used in paymentMethods call, e.g. US") + channel: String @doc(description: "The platform where a payment transaction takes place, e.g. Web") ) : AdyenPaymentMethods @resolver(class: "Adyen\\Payment\\Model\\Resolver\\GetAdyenPaymentMethods") adyenPaymentMethodsBalance (