diff --git a/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php b/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php new file mode 100644 index 000000000000..23ca1d88e362 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Model/BraintreeDataProvider.php @@ -0,0 +1,49 @@ +config = $config; + $this->adapterFactory = $adapterFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + + if (!$this->config->isActive($storeId)) { + throw new GraphQlInputException(__('The Braintree payment method is not active.')); + } + + $params = []; + $merchantAccountId = $this->config->getMerchantAccountId($storeId); + if (!empty($merchantAccountId)) { + $params[PaymentDataBuilder::MERCHANT_ACCOUNT_ID] = $merchantAccountId; + } + + return $this->adapterFactory->create($storeId)->generate($params); + } +} diff --git a/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php b/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php new file mode 100644 index 000000000000..1dea9992c630 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/Plugin/SetVaultPaymentNonce.php @@ -0,0 +1,79 @@ +command = $command; + $this->logger = $logger; + } + + /** + * Set Braintree nonce from public hash + * + * @param \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject + * @param \Magento\Quote\Model\Quote $quote + * @param array $paymentData + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeExecute( + \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject, + \Magento\Quote\Model\Quote $quote, + array $paymentData + ): array { + if ($paymentData['code'] !== ConfigProvider::CC_VAULT_CODE + || !isset($paymentData[ConfigProvider::CC_VAULT_CODE]) + || !isset($paymentData[ConfigProvider::CC_VAULT_CODE]['public_hash']) + ) { + return [$quote, $paymentData]; + } + + $subject = [ + 'public_hash' => $paymentData[ConfigProvider::CC_VAULT_CODE]['public_hash'], + 'customer_id' => $quote->getCustomerId(), + 'store_id' => $quote->getStoreId(), + ]; + + try { + $result = $this->command->execute($subject)->get(); + $paymentData[ConfigProvider::CC_VAULT_CODE]['payment_method_nonce'] = $result['paymentMethodNonce']; + } catch (\Exception $e) { + $this->logger->critical($e); + throw new GraphQlInputException(__('Sorry, but something went wrong')); + } + + return [$quote, $paymentData]; + } +} diff --git a/app/code/Magento/BraintreeGraphQl/README.md b/app/code/Magento/BraintreeGraphQl/README.md new file mode 100644 index 000000000000..f6740e4d250e --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/README.md @@ -0,0 +1,4 @@ +# BraintreeGraphQl + +**BraintreeGraphQl** provides type and resolver for method additional +information. \ No newline at end of file diff --git a/app/code/Magento/BraintreeGraphQl/composer.json b/app/code/Magento/BraintreeGraphQl/composer.json new file mode 100644 index 000000000000..7790a4ac031d --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/composer.json @@ -0,0 +1,28 @@ +{ + "name": "magento/module-braintree-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-braintree": "*", + "magento/module-store": "*", + "magento/module-quote": "*", + "magento/module-quote-graph-ql": "*" + }, + "suggest": { + "magento/module-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\BraintreeGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml b/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml new file mode 100644 index 000000000000..a31066316377 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/graphql/di.xml @@ -0,0 +1,20 @@ + + + + + + + Magento\BraintreeGraphQl\Model\BraintreeDataProvider + Magento\BraintreeGraphQl\Model\BraintreeVaultDataProvider + + + + + + + diff --git a/app/code/Magento/BraintreeGraphQl/etc/module.xml b/app/code/Magento/BraintreeGraphQl/etc/module.xml new file mode 100644 index 000000000000..dd12c0638b7b --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/module.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls b/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls new file mode 100644 index 000000000000..0492f8aaf989 --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/etc/schema.graphqls @@ -0,0 +1,22 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Mutation { + createBraintreeClientToken: String! @resolver(class: "\\Magento\\BraintreeGraphQl\\Model\\Resolver\\CreateBraintreeClientToken") @doc(description:"Creates Braintree Client Token for creating client-side nonce.") +} + +input PaymentMethodInput { + braintree: BraintreeInput + braintree_cc_vault: BraintreeCcVaultInput +} + +input BraintreeInput { + payment_method_nonce: String! + is_active_payment_token_enabler: Boolean! + device_data: String +} + +input BraintreeCcVaultInput { + public_hash: String! + device_data: String +} diff --git a/app/code/Magento/BraintreeGraphQl/registration.php b/app/code/Magento/BraintreeGraphQl/registration.php new file mode 100644 index 000000000000..37f7ef30864c --- /dev/null +++ b/app/code/Magento/BraintreeGraphQl/registration.php @@ -0,0 +1,10 @@ +mockResponseDataProvider = $mockResponseDataProvider; + } + + /** + * @param string $token + * @return \Braintree\Result\Successful|\Braintree\Result\Error + */ + public function createNonce($token) + { + return $this->mockResponseDataProvider->generateMockNonceResponse($token); + } + + /** + * @param array $attributes + * @return \Braintree\Result\Successful|\Braintree\Result\Error + */ + public function sale(array $attributes) + { + return $this->mockResponseDataProvider->generateMockSaleResponse($attributes); + } + + /** + * @param array $params + * @return string|\Braintree\Result\Error + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function generate(array $params = []) + { + return $this->mockResponseDataProvider->generateMockClientToken(); + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleBraintree/Model/MockResponseDataProvider.php b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/Model/MockResponseDataProvider.php new file mode 100644 index 000000000000..ab86109c6f91 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/Model/MockResponseDataProvider.php @@ -0,0 +1,161 @@ +random = $random; + } + + /** + * Create mock sale response for testing + * + * @param array $attributes + * @return \Braintree\Result\Error|\Braintree\Result\Successful + */ + public function generateMockSaleResponse(array $attributes) + { + if (empty($attributes['paymentMethodNonce'])) { + return new \Braintree\Result\Error( + [ + 'errors' => [ + [ + 'errorData' => [ + 'code' => 2019, + 'message' => 'Your transaction has been declined.' + ] + ] + ], + 'transaction' => $this->createTransaction($attributes)->jsonSerialize(), + ] + ); + } + + $transaction = $this->createTransaction($attributes); + + return new \Braintree\Result\Successful([$transaction]); + } + + /** + * Create mock nonce response for testing + * + * @param string $token + * @return \Braintree\Instance + */ + public function generateMockNonceResponse(string $token): \Braintree\Instance + { + $nonce = $this->createNonce($token); + + return new \Braintree\Result\Successful($nonce, 'paymentMethodNonce'); + } + + /** + * Create mock client token + * + * @return string + */ + public function generateMockClientToken(): string + { + return $this->random->getRandomString(32); + } + + /** + * Create Braintree transaction from provided request attributes + * + * @param array $attributes + * @return \Braintree\Transaction + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function createTransaction(array $attributes): \Braintree\Transaction + { + $creditCardInfo = $this->generateCardDetails(); + return \Braintree\Transaction::factory( + [ + 'amount' => $attributes['amount'], + 'billing' => $attributes['billing'] ?? null, + 'creditCard' => $creditCardInfo, + 'cardDetails' => new \Braintree\Transaction\CreditCardDetails($creditCardInfo), + 'currencyIsoCode' => 'USD', + 'customer' => $attributes['customer'], + 'cvvResponseCode' => 'M', + 'id' => $this->random->getRandomString(8), + 'options' => $attributes['options'] ?? null, + 'shipping' => $attributes['shipping'] ?? null, + 'paymentMethodNonce' => $attributes['paymentMethodNonce'], + 'status' => 'authorized', + 'type' => 'sale', + ] + ); + } + + /** + * Generate fake Braintree card details + * + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function generateCardDetails(): array + { + return [ + 'bin' => $this->random->getRandomString(6), + 'cardType' => 'Visa', + 'expirationMonth' => '12', + 'expirationYear' => '2020', //TODO: make dynamic + 'last4' => (string) random_int(1000, 9999), + 'token' => $this->random->getRandomString(6), + 'uniqueNumberIdentifier' => $this->random->getRandomString(32), + ]; + } + + /** + * Create fake Braintree nonce + * + * @param string $token + * @return \Braintree\PaymentMethodNonce + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function createNonce(string $token): \Braintree\PaymentMethodNonce + { + $lastFour = (string) random_int(1000, 9999); + $lastTwo = substr($lastFour, -2); + return \Braintree\PaymentMethodNonce::factory( + [ + 'bin' => $token, + 'consumed' => false, + 'default' => true, + 'description' => 'ending in ' . $lastTwo, + 'details' => [ + 'bin' => $this->random->getRandomString(6), + 'cardType' => 'Visa', + 'lastFour' => $lastFour, + 'lastTwo' => $lastTwo, + ], + 'hasSubscription' => false, + 'isLocked' => false, + 'nonce' => $this->random->getRandomString(36), + 'type' => 'CreditCard' + ] + ); + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/di.xml new file mode 100644 index 000000000000..db5c13684068 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/di.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/module.xml new file mode 100644 index 000000000000..22df4e5fe7ea --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/etc/module.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/dev/tests/api-functional/_files/Magento/TestModuleBraintree/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/registration.php new file mode 100644 index 000000000000..c95e68f3f48f --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleBraintree/registration.php @@ -0,0 +1,11 @@ +getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleBraintree') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleBraintree', __DIR__); +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/CreateBraintreeClientTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/CreateBraintreeClientTokenTest.php new file mode 100644 index 000000000000..1564d00fa599 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/CreateBraintreeClientTokenTest.php @@ -0,0 +1,53 @@ +graphQlMutation($this->getMutation()); + + self::assertArrayHasKey('createBraintreeClientToken', $response); + self::assertNotEmpty($response['createBraintreeClientToken']); + } + + /** + * Test creating Braintree client token when method is disabled + * + * @expectedException \Exception + * @expectedExceptionMessage payment method is not active + */ + public function testCreateBraintreeClientTokenNotActive() + { + $this->graphQlMutation($this->getMutation()); + } + + private function getMutation(): string + { + return <<getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + $this->tokenCollectionFactory = Bootstrap::getObjectManager()->get(TokenCollectionFactory::class); + $this->tokenResource = Bootstrap::getObjectManager()->get(PaymentToken::class); + $this->getNonceCommand = Bootstrap::getObjectManager()->get(GetPaymentNonceCommand::class); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrder() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQuery($maskedQuoteId); + $setPaymentResponse = $this->graphQlMutation($setPaymentQuery, [], '', $this->getHeaderMap()); + + $this->assertSetPaymentMethodResponse($setPaymentResponse, 'braintree'); + + $placeOrderQuery = $this->getPlaceOrderQuery($maskedQuoteId); + $placeOrderResponse = $this->graphQlMutation($placeOrderQuery, [], '', $this->getHeaderMap()); + + $this->assertPlaceOrderResponse($placeOrderResponse, $reservedOrderId); + + $tokenQueryResult = $this->graphQlQuery($this->getPaymentTokenQuery(), [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('customerPaymentTokens', $tokenQueryResult); + self::assertArrayHasKey('items', $tokenQueryResult['customerPaymentTokens']); + self::assertCount(0, $tokenQueryResult['customerPaymentTokens']['items']); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree_cc_vault/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderSaveInVault() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQuery($maskedQuoteId, true); + $setPaymentResponse = $this->graphQlMutation($setPaymentQuery, [], '', $this->getHeaderMap()); + + $this->assertSetPaymentMethodResponse($setPaymentResponse, 'braintree'); + + $placeOrderQuery = $this->getPlaceOrderQuery($maskedQuoteId); + $placeOrderResponse = $this->graphQlMutation($placeOrderQuery, [], '', $this->getHeaderMap()); + + $this->assertPlaceOrderResponse($placeOrderResponse, $reservedOrderId); + + $tokenQueryResult = $this->graphQlQuery($this->getPaymentTokenQuery(), [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('customerPaymentTokens', $tokenQueryResult); + self::assertArrayHasKey('items', $tokenQueryResult['customerPaymentTokens']); + self::assertCount(1, $tokenQueryResult['customerPaymentTokens']['items']); + $token = current($tokenQueryResult['customerPaymentTokens']['items']); + self::assertArrayHasKey('payment_method_code', $token); + self::assertEquals('braintree', $token['payment_method_code']); + self::assertArrayHasKey('type', $token); + self::assertEquals('card', $token['type']); + self::assertArrayHasKey('details', $token); + self::assertArrayHasKey('public_hash', $token); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree_cc_vault/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Braintree/_files/token.php + */ + public function testPlaceOrderWithVault() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeVaultQuery( + $maskedQuoteId, + 'braintree_public_hash' + ); + $setPaymentResponse = $this->graphQlMutation($setPaymentQuery, [], '', $this->getHeaderMap()); + + $this->assertSetPaymentMethodResponse($setPaymentResponse, 'braintree_cc_vault'); + + $placeOrderQuery = $this->getPlaceOrderQuery($maskedQuoteId); + $placeOrderResponse = $this->graphQlMutation($placeOrderQuery, [], '', $this->getHeaderMap()); + + $this->assertPlaceOrderResponse($placeOrderResponse, $reservedOrderId); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree_cc_vault/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @dataProvider dataProviderTestSetPaymentMethodInvalidInput + * @expectedException \Exception + * @param string $methodCode + */ + public function testSetPaymentMethodInvalidInput(string $methodCode) + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQueryInvalidInput( + $maskedQuoteId, + $methodCode + ); + $this->expectExceptionMessage("Required parameter \"$methodCode\" for \"payment_method\" is missing."); + $this->graphQlMutation($setPaymentQuery, [], '', $this->getHeaderMap()); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree_cc_vault/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @dataProvider dataProviderTestSetPaymentMethodInvalidInput + * @expectedException \Exception + * @param string $methodCode + */ + public function testSetPaymentMethodInvalidMethodInput(string $methodCode) + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQueryInvalidMethodInput( + $maskedQuoteId, + $methodCode + ); + $this->expectExceptionMessage("for \"$methodCode\" is missing."); + $this->graphQlMutation($setPaymentQuery, [], '', $this->getHeaderMap()); + } + + public function dataProviderTestSetPaymentMethodInvalidInput(): array + { + return [ + ['braintree'], + ['braintree_cc_vault'], + ]; + } + + private function assertPlaceOrderResponse(array $response, string $reservedOrderId): void + { + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_id']); + } + + private function assertSetPaymentMethodResponse(array $response, string $methodCode): void + { + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + } + + /** + * @param string $maskedQuoteId + * @param bool $saveInVault + * @return string + */ + private function getSetPaymentBraintreeQuery(string $maskedQuoteId, bool $saveInVault = false): string + { + $saveInVault = json_encode($saveInVault); + return <<customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } + + /** + * @inheritdoc + */ + public function tearDown() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + + $tokenCollection = $this->tokenCollectionFactory->create(); + foreach ($tokenCollection as $token) { + $this->tokenResource->delete($token); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/Guest/SetPaymentMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/Guest/SetPaymentMethodTest.php new file mode 100644 index 000000000000..1d48c5253fe8 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Braintree/Guest/SetPaymentMethodTest.php @@ -0,0 +1,286 @@ +getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @dataProvider dataProviderTestPlaceOrder + */ + public function testPlaceOrder(string $nonce) + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQuery($maskedQuoteId, $nonce); + $setPaymentResponse = $this->graphQlMutation($setPaymentQuery); + + $this->assertSetPaymentMethodResponse($setPaymentResponse, 'braintree'); + + $placeOrderQuery = $this->getPlaceOrderQuery($maskedQuoteId); + $placeOrderResponse = $this->graphQlMutation($placeOrderQuery); + + $this->assertPlaceOrderResponse($placeOrderResponse, $reservedOrderId); + } + + /** + * Data provider for testPlaceOrder + * + * @return array + */ + public function dataProviderTestPlaceOrder(): array + { + return [ + ['fake-valid-nonce'], + ['fake-apple-pay-visa-nonce'], + ]; + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @expectedException \Exception + */ + public function testSetPaymentMethodInvalidInput() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQueryInvalidInput($maskedQuoteId); + $this->expectExceptionMessage("Required parameter \"braintree\" for \"payment_method\" is missing."); + $this->graphQlMutation($setPaymentQuery); + } + + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/braintree/active 1 + * @magentoConfigFixture default_store payment/braintree/environment sandbox + * @magentoConfigFixture default_store payment/braintree/merchant_id def_merchant_id + * @magentoConfigFixture default_store payment/braintree/public_key def_public_key + * @magentoConfigFixture default_store payment/braintree/private_key def_private_key + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @expectedException \Exception + */ + public function testSetPaymentMethodInvalidMethodInput() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentQuery = $this->getSetPaymentBraintreeQueryInvalidMethodInput($maskedQuoteId); + $this->expectExceptionMessage("for \"braintree\" is missing."); + $this->graphQlMutation($setPaymentQuery); + } + + private function assertPlaceOrderResponse(array $response, string $reservedOrderId): void + { + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_id']); + } + + private function assertSetPaymentMethodResponse(array $response, string $methodCode): void + { + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getSetPaymentBraintreeQuery(string $maskedQuoteId, string $nonce): string + { + return <<registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Braintree/_files/token.php b/dev/tests/integration/testsuite/Magento/GraphQl/Braintree/_files/token.php new file mode 100644 index 000000000000..8164e6d33e24 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Braintree/_files/token.php @@ -0,0 +1,45 @@ +get(\Magento\Braintree\Model\Adapter\BraintreeAdapterFactory::class); +$adapter = $adapterFactory->create(); + +$result = $adapter->sale( + [ + 'amount' => '0.01', + 'customer' => [ + 'email' => 'customer@example.com', + 'firstName' => 'John', + 'lastName' => 'Smith' + ], + 'options' => ['storeInVaultOnSuccess' => true], + 'paymentMethodNonce' => 'fake-valid-nonce', + ] +); + +$braintreeToken = $result->transaction->creditCardDetails->token; + +/** @var PaymentToken $token */ +$token = $objectManager->create(PaymentToken::class); + +$token->setGatewayToken($braintreeToken) + ->setPublicHash('braintree_public_hash') + ->setPaymentMethodCode('braintree_vault') + ->setType('card') + ->setExpiresAt(strtotime('+1 year')) + ->setIsVisible(true) + ->setIsActive(true) + ->setCustomerId(1); + +/** @var PaymentTokenRepository $tokenRepository */ +$tokenRepository = $objectManager->create(PaymentTokenRepository::class); +$token = $tokenRepository->save($token);