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);