diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentToken/PaymentTokenDataProvider.php b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentToken/PaymentTokenDataProvider.php new file mode 100644 index 000000000000..0b9d655c02ca --- /dev/null +++ b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentToken/PaymentTokenDataProvider.php @@ -0,0 +1,130 @@ +serviceOutputProcessor = $serviceOutputProcessor; + $this->jsonSerializer = $jsonSerializer; + $this->dataObjectHelper = $dataObjectHelper; + } + + /** + * Transform array of payment token data to array of object array format + * + * @param PaymentTokenInterface[] $paymentTokens + * @return array + */ + public function processPaymentTokenArray(array $paymentTokens): array + { + $result = []; + /** @var PaymentTokenInterface $paymentToken */ + foreach ($paymentTokens as $paymentToken) { + $result[] = $this->processPaymentToken($paymentToken); + } + return $result; + } + + /** + * Transform single payment token data from object to an array format + * + * @param PaymentTokenInterface $paymentTokenObject + * @return array + */ + public function processPaymentToken(PaymentTokenInterface $paymentTokenObject): array + { + $paymentToken = $this->serviceOutputProcessor->process( + $paymentTokenObject, + PaymentTokenRepositoryInterface::class, + 'getById' + ); + $detailsAttributes = []; + if (!empty($paymentTokenObject->getTokenDetails())) { + $details = $this->jsonSerializer->unserialize($paymentTokenObject->getTokenDetails()); + foreach ($details as $key => $attribute) { + $isArray = false; + if (is_array($attribute)) { + $isArray = true; + foreach ($attribute as $attributeValue) { + if (is_array($attributeValue)) { + $detailsAttributes[] = [ + 'code' => $key, + 'value' => $this->jsonSerializer->serialize($attribute) + ]; + continue; + } + $detailsAttributes[] = ['code' => $key, 'value' => implode(',', $attribute)]; + continue; + } + } + if ($isArray) { + continue; + } + $detailsAttributes[] = ['code' => $key, 'value' => $attribute]; + } + } + $paymentToken['details'] = $detailsAttributes; + return $paymentToken; + } + + /** + * Add $tokenInput array information to a $token object + * + * @param PaymentTokenInterface $token + * @param array $tokenInput + * @return PaymentTokenInterface + */ + public function fillPaymentToken(PaymentTokenInterface $token, array $tokenInput): PaymentTokenInterface + { + $this->dataObjectHelper->populateWithArray( + $token, + $tokenInput, + PaymentTokenInterface::class + ); + $tokenDetails = []; + foreach ($tokenInput['details'] as $attribute) { + $tokenDetails[$attribute['code']] = $attribute['value']; + } + $token->setTokenDetails($this->jsonSerializer->serialize($tokenDetails)); + return $token; + } +} diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenAdd.php b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenAdd.php new file mode 100644 index 000000000000..65376b1c4fde --- /dev/null +++ b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenAdd.php @@ -0,0 +1,150 @@ +paymentTokenRepository = $paymentTokenRepository; + $this->paymentTokenManagement = $paymentTokenManagement; + $this->paymentTokenFactory = $paymentTokenFactory; + $this->paymentTokenDataProvider = $paymentTokenDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + /** @var ContextInterface $context */ + if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { + throw new GraphQlAuthorizationException( + __( + 'A guest customer cannot access resource "%1".', + ['store_payment_token'] + ) + ); + } + $customerId = $context->getUserId(); + return $this->paymentTokenDataProvider->processPaymentToken( + $this->processPaymentTokenAdd($customerId, $args['input']) + ); + } + + /** + * Check input data + * + * @param array $tokenInfo + * @return bool|string + */ + private function getInputError(array $tokenInfo) + { + foreach (self::REQUIRED_ATTRIBUTES as $attributeName) { + if (!isset($tokenInfo[$attributeName]) || empty($tokenInfo[$attributeName])) { + return $attributeName; + } + } + return false; + } + + /** + * Process payment token add + * + * @param int $customerId + * @param array $tokenInfo + * @return PaymentTokenInterface + * @throws GraphQlInputException + * @throws GraphQlAlreadyExistsException + */ + private function processPaymentTokenAdd($customerId, array $tokenInfo): PaymentTokenInterface + { + $errorInput = $this->getInputError($tokenInfo); + if ($errorInput) { + throw new GraphQlInputException( + __('The required parameter %1 is missing.', [$errorInput]) + ); + } + /** @var PaymentTokenInterface $token */ + $token = $this->paymentTokenDataProvider->fillPaymentToken( + $this->paymentTokenFactory->create($tokenInfo['type']), + $tokenInfo + ); + $token->setCustomerId($customerId); + try { + $this->paymentTokenRepository->save($token); + // Reload current token object from repository to get "created_at" updated + return $this->paymentTokenManagement->getByPublicHash($token->getPublicHash(), $customerId); + } catch (AlreadyExistsException $e) { + throw new GraphQlAlreadyExistsException(__($e->getMessage()), $e); + } + } +} diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenDelete.php b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenDelete.php new file mode 100644 index 000000000000..853459b668b0 --- /dev/null +++ b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokenDelete.php @@ -0,0 +1,89 @@ +paymentTokenRepository = $paymentTokenRepository; + $this->paymentTokenManagement = $paymentTokenManagement; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + /** @var ContextInterface $context */ + if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { + throw new GraphQlAuthorizationException( + __( + 'A guest customer cannot access resource "%1".', + ['store_payment_token'] + ) + ); + } + $customerId = $context->getUserId(); + return $this->deleteToken($customerId, $args['public_hash']); + } + + /** + * Process delete request + * + * @param int $customerId + * @param string $publicHash + * @return bool + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + */ + private function deleteToken($customerId, $publicHash) + { + $token = $this->paymentTokenManagement->getByPublicHash($publicHash, $customerId); + if (!$token) { + throw new GraphQlNoSuchEntityException( + __('Payment token public_hash %1 does not exist.', [$publicHash]) + ); + } + return $this->paymentTokenRepository->delete($token); + } +} diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php new file mode 100644 index 000000000000..06ccba83de9b --- /dev/null +++ b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php @@ -0,0 +1,70 @@ +paymentTokenDataProvider = $paymentTokenDataProvider; + $this->paymentTokenManagement = $paymentTokenManagement; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + /** @var ContextInterface $context */ + if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { + throw new GraphQlAuthorizationException( + __( + 'A guest customer cannot access resource "%1".', + ['store_payment_token'] + ) + ); + } + $customerId = $context->getUserId(); + return $this->paymentTokenDataProvider->processPaymentTokenArray( + $this->paymentTokenManagement->getVisibleAvailableTokens($customerId) + ); + } +} diff --git a/app/code/Magento/VaultGraphQl/README.md b/app/code/Magento/VaultGraphQl/README.md new file mode 100644 index 000000000000..f1a92458d37d --- /dev/null +++ b/app/code/Magento/VaultGraphQl/README.md @@ -0,0 +1,4 @@ +# VaultGraphQl + +**VaultGraphQl** provides type and resolver information for the GraphQl module +to generate vault information endpoints. diff --git a/app/code/Magento/VaultGraphQl/composer.json b/app/code/Magento/VaultGraphQl/composer.json new file mode 100644 index 000000000000..56dbdb402d17 --- /dev/null +++ b/app/code/Magento/VaultGraphQl/composer.json @@ -0,0 +1,26 @@ +{ + "name": "magento/module-vault-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-authorization": "*", + "magento/module-vault": "*" + }, + "suggest": { + "magento/module-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\VaultGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/VaultGraphQl/etc/module.xml b/app/code/Magento/VaultGraphQl/etc/module.xml new file mode 100644 index 000000000000..990519282ffe --- /dev/null +++ b/app/code/Magento/VaultGraphQl/etc/module.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/app/code/Magento/VaultGraphQl/etc/schema.graphqls b/app/code/Magento/VaultGraphQl/etc/schema.graphqls new file mode 100644 index 000000000000..5836196226f0 --- /dev/null +++ b/app/code/Magento/VaultGraphQl/etc/schema.graphqls @@ -0,0 +1,44 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Query { + paymentTokens: [PaymentToken] @resolver(class: "Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens") @doc(description: "The paymentTokens query returns a list of stored payment methods for a logged-in customer") +} + +type Mutation { + paymentTokenAdd(input: PaymentTokenInput!): PaymentToken @resolver(class: "Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokenAdd") @doc(description: "Add payment tokens") + paymentTokenDelete(public_hash: String!): Boolean @resolver(class: "Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokenDelete") @doc(description: "Remove stored payment tokens") +} + +type PaymentToken @doc(description: "PaymentToken defines stored payment details") { + public_hash: String! @doc(description: "The public hash") + payment_method_code: String! @doc(description: "The payment method code") + type: PaymentTokenType! @doc(description: "The payment token type") + created_at: String! @doc(description: "Timestamp indicating when the payment token was created") + expires_at: String! @doc(description: "Timestamp indicating when the payment token was updated") + details: [DetailsAttribute] @doc(description: "Payment token details") +} + +type DetailsAttribute { + code: String! @doc(description: "Attribute code") + value: String! @doc(description: "Attribute value") +} + +input PaymentTokenInput { + public_hash: String! @doc(description: "The public hash") + payment_method_code: String! @doc(description: "The payment method code") + type: PaymentTokenType! @doc(description: "The payment token type") + expires_at: String @doc(description: "Timestamp indicating when the payment token was updated") + gateway_token: String! @doc(description: "The getaway payment token") + details: [DetailsAttributeInput] @doc(description: "Payment token details") +} + +input DetailsAttributeInput { + code: String! @doc(description: "Attribute code") + value: String! @doc(description: "Attribute value") +} + +enum PaymentTokenType @doc(description: "Token types availables"){ + card @doc(description: "Card token type") + account @doc(description: "Account token type") +} \ No newline at end of file diff --git a/app/code/Magento/VaultGraphQl/registration.php b/app/code/Magento/VaultGraphQl/registration.php new file mode 100644 index 000000000000..eb0b5f31d515 --- /dev/null +++ b/app/code/Magento/VaultGraphQl/registration.php @@ -0,0 +1,9 @@ +=5.4.0", "symfony/console": "^2.8|^3|^4", "symfony/finder": "^2.5|^3|^4" @@ -5010,7 +5011,7 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-05-25T18:02:34+00:00" + "time": "2018-10-19T22:35:38+00:00" }, { "name": "consolidation/robo", @@ -5095,16 +5096,16 @@ }, { "name": "consolidation/self-update", - "version": "1.1.3", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/consolidation/self-update.git", - "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318" + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/self-update/zipball/de33822f907e0beb0ffad24cf4b1b4fae5ada318", - "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54", + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54", "shasum": "" }, "require": { @@ -5141,7 +5142,7 @@ } ], "description": "Provides a self:update command for Symfony Console applications.", - "time": "2018-08-24T17:01:46+00:00" + "time": "2018-10-28T01:52:03+00:00" }, { "name": "dflydev/dot-access-data", @@ -5594,16 +5595,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.13.0", + "version": "v2.13.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "7136aa4e0c5f912e8af82383775460d906168a10" + "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/7136aa4e0c5f912e8af82383775460d906168a10", - "reference": "7136aa4e0c5f912e8af82383775460d906168a10", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/54814c62d5beef3ba55297b9b3186ed8b8a1b161", + "reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161", "shasum": "" }, "require": { @@ -5614,7 +5615,7 @@ "ext-tokenizer": "*", "php": "^5.6 || >=7.0 <7.3", "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.2 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6", "symfony/event-dispatcher": "^3.0 || ^4.0", "symfony/filesystem": "^3.0 || ^4.0", "symfony/finder": "^3.0 || ^4.0", @@ -5650,11 +5651,6 @@ "php-cs-fixer" ], "type": "application", - "extra": { - "branch-alias": { - "dev-master": "2.13-dev" - } - }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -5686,7 +5682,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2018-08-23T13:15:44+00:00" + "time": "2018-10-21T00:32:10+00:00" }, { "name": "fzaninotto/faker", @@ -6049,16 +6045,16 @@ }, { "name": "jms/metadata", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/e5854ab1aa643623dc64adde718a8eec32b957a8", + "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8", "shasum": "" }, "require": { @@ -6081,9 +6077,13 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, { "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" @@ -6096,7 +6096,7 @@ "xml", "yaml" ], - "time": "2016-12-05T10:18:33+00:00" + "time": "2018-10-26T12:40:10+00:00" }, { "name": "jms/parser-lib", @@ -8228,7 +8228,7 @@ }, { "name": "symfony/browser-kit", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", @@ -8285,16 +8285,16 @@ }, { "name": "symfony/config", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "76015a3cc372b14d00040ff58e18e29f69eba717" + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/76015a3cc372b14d00040ff58e18e29f69eba717", - "reference": "76015a3cc372b14d00040ff58e18e29f69eba717", + "url": "https://api.github.com/repos/symfony/config/zipball/b3d4d7b567d7a49e6dfafb6d4760abc921177c96", + "reference": "b3d4d7b567d7a49e6dfafb6d4760abc921177c96", "shasum": "" }, "require": { @@ -8344,20 +8344,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-08-08T06:37:38+00:00" + "time": "2018-09-08T13:24:10+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2a4df7618f869b456f9096781e78c57b509d76c7" + "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a4df7618f869b456f9096781e78c57b509d76c7", - "reference": "2a4df7618f869b456f9096781e78c57b509d76c7", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d67de79a70a27d93c92c47f37ece958bf8de4d8a", + "reference": "d67de79a70a27d93c92c47f37ece958bf8de4d8a", "shasum": "" }, "require": { @@ -8397,20 +8397,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:10:45+00:00" + "time": "2018-10-02T16:36:10+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "bae4983003c9d451e278504d7d9b9d7fc1846873" + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/bae4983003c9d451e278504d7d9b9d7fc1846873", - "reference": "bae4983003c9d451e278504d7d9b9d7fc1846873", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f6b9d893ad28aefd8942dc0469c8397e2216fe30", + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30", "shasum": "" }, "require": { @@ -8468,20 +8468,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-08-08T11:48:58+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "1c4519d257e652404c3aa550207ccd8ada66b38e" + "reference": "80e60271bb288de2a2259662cff125cff4f93f95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1c4519d257e652404c3aa550207ccd8ada66b38e", - "reference": "1c4519d257e652404c3aa550207ccd8ada66b38e", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/80e60271bb288de2a2259662cff125cff4f93f95", + "reference": "80e60271bb288de2a2259662cff125cff4f93f95", "shasum": "" }, "require": { @@ -8525,20 +8525,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:00:49+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3a5c91e133b220bb882b3cd773ba91bf39989345" + "reference": "d528136617ff24f530e70df9605acc1b788b08d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3a5c91e133b220bb882b3cd773ba91bf39989345", - "reference": "3a5c91e133b220bb882b3cd773ba91bf39989345", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d528136617ff24f530e70df9605acc1b788b08d4", + "reference": "d528136617ff24f530e70df9605acc1b788b08d4", "shasum": "" }, "require": { @@ -8579,20 +8579,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-08-27T17:47:02+00:00" + "time": "2018-10-03T08:48:45+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "1913f1962477cdbb13df951f8147d5da1fe2412c" + "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/1913f1962477cdbb13df951f8147d5da1fe2412c", - "reference": "1913f1962477cdbb13df951f8147d5da1fe2412c", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40f0e40d37c1c8a762334618dea597d64bbb75ff", + "reference": "40f0e40d37c1c8a762334618dea597d64bbb75ff", "shasum": "" }, "require": { @@ -8633,7 +8633,7 @@ "configuration", "options" ], - "time": "2018-07-26T08:55:25+00:00" + "time": "2018-09-18T12:45:12+00:00" }, { "name": "symfony/polyfill-php70", @@ -8751,16 +8751,16 @@ }, { "name": "symfony/stopwatch", - "version": "v4.1.4", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "966c982df3cca41324253dc0c7ffe76b6076b705" + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/966c982df3cca41324253dc0c7ffe76b6076b705", - "reference": "966c982df3cca41324253dc0c7ffe76b6076b705", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", "shasum": "" }, "require": { @@ -8796,20 +8796,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:00:49+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.15", + "version": "v3.4.17", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8" + "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/c2f4812ead9f847cb69e90917ca7502e6892d6b8", - "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8", + "url": "https://api.github.com/repos/symfony/yaml/zipball/640b6c27fed4066d64b64d5903a86043f4a4de7f", + "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f", "shasum": "" }, "require": { @@ -8855,7 +8855,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-08-10T07:34:36+00:00" + "time": "2018-10-02T16:33:53+00:00" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenAddTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenAddTest.php new file mode 100644 index 000000000000..c40270da926e --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenAddTest.php @@ -0,0 +1,167 @@ +get(CustomerTokenServiceInterface::class); + $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = ObjectManager::getInstance()->get(CustomerRepositoryInterface::class); + $customer = $customerRepository->get($userName); + $paymentTokenInfo = [ + 'public_hash' => '123456789', + 'payment_method_code' => '987654321', + 'type' => 'card', + 'expires_at' => '2018-12-31 23:59:59', + 'gateway_token' => 'ABC123', + 'details' => [ + ['code' => 'type', 'value' => 'VI'], + ['code' => 'maskedCC', 'value' => '9876'], + ['code' => 'expirationDate', 'value' => '12/2055'], + + ] + ]; + $query + = <<graphQlQuery($query, [], '', $headerMap); + $this->assertTrue(is_array($response)); + $this->assertArrayHasKey('paymentTokenAdd', $response); + $tokenPublicHash = $response['paymentTokenAdd']['public_hash']; + /** @var PaymentTokenManagementInterface $tokenManager */ + $tokenManager = ObjectManager::getInstance()->get(PaymentTokenManagementInterface::class); + /** @var PaymentTokenInterface $token */ + $token = $tokenManager->getByPublicHash($tokenPublicHash, $customer->getId()); + $this->assertPaymentTokenFields($token, $response['paymentTokenAdd']); + $this->assertPaymentTokenFields($token, $paymentTokenInfo); + } + + /** + * Verify add payment token without credentials + */ + public function testAddPaymentTokenWithoutCredentials() + { + $query + = <<expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors:' . ' ' . + 'A guest customer cannot access resource "store_payment_token".'); + $this->graphQlQuery($query); + } + + /** + * Verify the fields for Customer address + * + * @param PaymentTokenInterface $paymentToken + * @param array $actualResponse + */ + private function assertPaymentTokenFields(PaymentTokenInterface $paymentToken, array $actualResponse) + { + $assertionMap = [ + ['response_field' => 'public_hash', 'expected_value' => $paymentToken->getPublicHash()], + ['response_field' => 'payment_method_code', 'expected_value' => $paymentToken->getPaymentMethodCode()], + ['response_field' => 'type', 'expected_value' => $paymentToken->getType()], + ['response_field' => 'expires_at', 'expected_value' => $paymentToken->getExpiresAt()], + ]; + $this->assertResponseFields($actualResponse, $assertionMap); + /** @var SerializerInterface $jsonSerializer */ + $jsonSerializer = ObjectManager::getInstance()->get(SerializerInterface::class); + $paymentTokenDetailArray = $jsonSerializer->unserialize($paymentToken->getTokenDetails()); + foreach ($actualResponse['details'] as $details) { + $this->assertEquals($paymentTokenDetailArray[$details['code']], $details['value']); + } + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenDeleteTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenDeleteTest.php new file mode 100644 index 000000000000..2ddd5fa07125 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokenDeleteTest.php @@ -0,0 +1,69 @@ +get(CustomerTokenServiceInterface::class); + $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = ObjectManager::getInstance()->get(CustomerRepositoryInterface::class); + $customer = $customerRepository->get($userName); + /** @var \Magento\Vault\Api\PaymentTokenManagementInterface $tokenRepository */ + $tokenRepository = ObjectManager::getInstance()->get(PaymentTokenManagementInterface::class); + /** @var \Magento\Vault\Api\Data\PaymentTokenInterface[] $tokenList */ + $tokenList = $tokenRepository->getListByCustomerId($customer->getId()); + /** @var \Magento\Vault\Api\Data\PaymentTokenInterface $token */ + $token = current($tokenList); + $tokenPublicHash = $token->getPublicHash(); + $query + = <<graphQlQuery($query, [], '', $headerMap); + $this->assertTrue(is_array($response)); + $this->assertArrayHasKey('paymentTokenDelete', $response); + $this->assertTrue($response['paymentTokenDelete']); + } + + /** + * Verify delete payment token without credentials + */ + public function testDeletePaymentTokenWithoutCredentials() + { + $query + = <<expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors:' . ' ' . + 'A guest customer cannot access resource "store_payment_token".'); + $this->graphQlQuery($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokensTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokensTest.php new file mode 100644 index 000000000000..be9186cc48ae --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/PaymentTokensTest.php @@ -0,0 +1,150 @@ +get(\Magento\Integration\Api\CustomerTokenServiceInterface::class); + $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = ObjectManager::getInstance()->get(CustomerRepositoryInterface::class); + $customer = $customerRepository->get($userName); + $response = $this->graphQlQuery($query, [], '', $headerMap); + $this->assertTrue(is_array($response['paymentTokens']), "paymentTokens field must be of an array type."); + $this->assertEquals( + $this->getPaymentTokenAmountFroCustomer($customer->getId()), + count($response['paymentTokens']) + ); + $list = $response['paymentTokens']; + + $this->assertEquals('H123456789', $list[0]['public_hash']); + $this->assertEquals('H987654321', $list[1]['public_hash']); + $this->assertEquals('H1122334455', $list[2]['public_hash']); + + $this->assertEquals('code_first', $list[0]['payment_method_code']); + $this->assertEquals('code_second', $list[1]['payment_method_code']); + $this->assertEquals('code_third', $list[2]['payment_method_code']); + + $this->assertEquals('card', $list[0]['type']); + $this->assertEquals('card', $list[1]['type']); + $this->assertEquals('account', $list[2]['type']); + + $this->assertIsDetailsArray($list); + $this->assertTokenDetails( + ['type' => 'VI', 'maskedCC' => '9876', 'expirationDate' => '12/2020'], + $list[0]['details'] + ); + $this->assertTokenDetails( + ['type' => 'MC', 'maskedCC' => '4444', 'expirationDate' => '12/2030'], + $list[1]['details'] + ); + $this->assertTokenDetails( + ['type' => 'DI', 'maskedCC' => '0001', 'expirationDate' => '12/2040'], + $list[2]['details'] + ); + } + + /** + * Verify store payment token without credentials + */ + public function testPaymentTokenWithoutCredentials() + { + $query + = <<expectException(\Exception::class); + $this->expectExceptionMessage('GraphQL response contains errors:' . ' ' . + 'A guest customer cannot access resource "store_payment_token".'); + $this->graphQlQuery($query); + } + + /** + * Verify is details is array + * + * @param array $response + */ + private function assertIsDetailsArray(array $response) + { + foreach ($response as $token) { + $this->assertTrue(is_array($token['details'])); + } + } + + /** + * Verify token details + * + * @param array $expected + * @param array $response + */ + private function assertTokenDetails(array $expected, array $response) + { + foreach ($response as $details) { + $this->assertEquals($expected[$details['code']], $details['value']); + } + } + + /** + * Get amount of customer payment token + * + * @param int $customerId + * @return int + */ + private function getPaymentTokenAmountFroCustomer($customerId) + { + /** @var \Magento\Vault\Model\PaymentTokenManagement $paymentTokenManagementInterface */ + $paymentTokenManagementInterface = ObjectManager::getInstance() + ->get(PaymentTokenManagementInterface::class); + return count($paymentTokenManagementInterface->getVisibleAvailableTokens($customerId)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens.php b/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens.php new file mode 100644 index 000000000000..2fcb31172a18 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens.php @@ -0,0 +1,62 @@ + 1, + 'public_hash' => 'H123456789', + 'payment_method_code' => 'code_first', + 'gateway_token' => 'ABC1234', + 'type' => 'card', + 'expires_at' => strtotime('+1 year'), + 'details' => '{"type":"VI","maskedCC":"9876","expirationDate":"12\/2020"}', + 'is_active' => 1 + ], + [ + 'customer_id' => 1, + 'public_hash' => 'H987654321', + 'payment_method_code' => 'code_second', + 'gateway_token' => 'ABC4567', + 'type' => 'card', + 'expires_at' => strtotime('+1 year'), + 'details' => '{"type":"MC","maskedCC":"4444","expirationDate":"12\/2030"}', + 'is_active' => 1 + ], + [ + 'customer_id' => 1, + 'public_hash' => 'H1122334455', + 'payment_method_code' => 'code_third', + 'gateway_token' => 'ABC7890', + 'type' => 'account', + 'expires_at' => strtotime('+1 year'), + 'details' => '{"type":"DI","maskedCC":"0001","expirationDate":"12\/2040"}', + 'is_active' => 1 + ], + [ + 'customer_id' => 1, + 'public_hash' => 'H5544332211', + 'payment_method_code' => 'code_fourth', + 'gateway_token' => 'ABC0000', + 'type' => 'account', + 'expires_at' => strtotime('+1 year'), + 'details' => '', + 'is_active' => 0 + ], +]; +/** @var array $tokenData */ +foreach ($paymentTokens as $tokenData) { + /** @var PaymentToken $bookmark */ + $paymentToken = $objectManager->create(PaymentToken::class); + $paymentToken + ->setData($tokenData) + ->save(); +} diff --git a/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens_rollback.php b/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens_rollback.php new file mode 100644 index 000000000000..0800ed8fc332 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Vault/_files/payment_active_tokens_rollback.php @@ -0,0 +1,27 @@ +get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +/** @var \Magento\Vault\Model\ResourceModel\PaymentToken $urlRewriteCollection */ +$paymentTokenCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Vault\Model\ResourceModel\PaymentToken\Collection::class); +$collection = $paymentTokenCollection + ->addFieldToFilter('public_hash', ['H123456789','H987654321','H1122334455','H5544332211']) + ->load() + ->walk('delete'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false);