diff --git a/app/code/Magento/PaypalGraphQl/Model/Plugin/Cart/PayflowPro/SetPaymentMethodOnCart.php b/app/code/Magento/PaypalGraphQl/Model/Plugin/Cart/PayflowPro/SetPaymentMethodOnCart.php
new file mode 100644
index 0000000000000..7ca4d41cfd33a
--- /dev/null
+++ b/app/code/Magento/PaypalGraphQl/Model/Plugin/Cart/PayflowPro/SetPaymentMethodOnCart.php
@@ -0,0 +1,70 @@
+paymentRepository = $paymentRepository;
+ $this->additionalDataProviderPool = $additionalDataProviderPool;
+ }
+
+ /**
+ * Set redirect URL paths on payment additionalInformation
+ *
+ * @param \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject
+ * @param mixed $result
+ * @param Quote $cart
+ * @param array $paymentData
+ * @return void
+ * @throws GraphQlInputException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterExecute(
+ \Magento\QuoteGraphQl\Model\Cart\SetPaymentMethodOnCart $subject,
+ $result,
+ Quote $cart,
+ array $paymentData
+ ): void {
+ $paymentData = $this->additionalDataProviderPool->getData(Config::METHOD_PAYFLOWPRO, $paymentData);
+ $cartCustomerId = (int)$cart->getCustomerId();
+ if ($cartCustomerId === 0 &&
+ array_key_exists(PayflowProSetCcData::IS_ACTIVE_PAYMENT_TOKEN_ENABLER, $paymentData)) {
+ $payment = $cart->getPayment();
+ $payment->unsAdditionalInformation(PayflowProSetCcData::IS_ACTIVE_PAYMENT_TOKEN_ENABLER);
+ $payment->save();
+ }
+ }
+}
diff --git a/app/code/Magento/PaypalGraphQl/Model/Resolver/PayflowProResponse.php b/app/code/Magento/PaypalGraphQl/Model/Resolver/PayflowProResponse.php
index ce44511c60f3e..b3ddced97aca6 100644
--- a/app/code/Magento/PaypalGraphQl/Model/Resolver/PayflowProResponse.php
+++ b/app/code/Magento/PaypalGraphQl/Model/Resolver/PayflowProResponse.php
@@ -126,9 +126,9 @@ public function resolve(
$this->parameters->fromString(urldecode($paypalPayload));
$data = $this->parameters->toArray();
try {
- $do = $this->dataObjectFactory->create(['data' => array_change_key_case($data, CASE_LOWER)]);
- $this->responseValidator->validate($do, $this->transparent);
- $this->transaction->savePaymentInQuote($do, $cart->getId());
+ $response = $this->transaction->getResponseObject($data);
+ $this->responseValidator->validate($response, $this->transparent);
+ $this->transaction->savePaymentInQuote($response, $cart->getId());
} catch (LocalizedException $exception) {
$parameters['error'] = true;
$parameters['error_msg'] = $exception->getMessage();
diff --git a/app/code/Magento/PaypalGraphQl/Observer/PayflowProSetCcData.php b/app/code/Magento/PaypalGraphQl/Observer/PayflowProSetCcData.php
new file mode 100644
index 0000000000000..801ec91d063c5
--- /dev/null
+++ b/app/code/Magento/PaypalGraphQl/Observer/PayflowProSetCcData.php
@@ -0,0 +1,88 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Set CcData
+ *
+ * @param Observer $observer
+ *
+ * @throws GraphQlInputException
+ */
+ public function execute(Observer $observer)
+ {
+ $dataObject = $this->readDataArgument($observer);
+ $additionalData = $dataObject->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
+ $paymentModel = $this->readPaymentModelArgument($observer);
+
+ if (!isset($additionalData['cc_details'])) {
+ return;
+ }
+
+ if ($this->isPayflowProVaultEnable()) {
+ if (!isset($additionalData[self::IS_ACTIVE_PAYMENT_TOKEN_ENABLER])) {
+ $paymentModel->setData(self::IS_ACTIVE_PAYMENT_TOKEN_ENABLER, false);
+ }
+
+ $paymentModel->setData(
+ self::IS_ACTIVE_PAYMENT_TOKEN_ENABLER,
+ $additionalData[self::IS_ACTIVE_PAYMENT_TOKEN_ENABLER]
+ );
+ } else {
+ $paymentModel->setData(self::IS_ACTIVE_PAYMENT_TOKEN_ENABLER, false);
+ }
+
+ $ccData = $additionalData['cc_details'];
+ $paymentModel->setCcType($ccData['cc_type']);
+ $paymentModel->setCcExpYear($ccData['cc_exp_year']);
+ $paymentModel->setCcExpMonth($ccData['cc_exp_month']);
+ $paymentModel->setCcLast4($ccData['cc_last_4']);
+ }
+
+ /**
+ * Check if payflowpro vault is enable
+ *
+ * @return bool
+ */
+ private function isPayflowProVaultEnable()
+ {
+ return (bool)$this->scopeConfig->getValue(self::XML_PATH_PAYMENT_PAYFLOWPRO_CC_VAULT_ACTIVE);
+ }
+}
diff --git a/app/code/Magento/PaypalGraphQl/composer.json b/app/code/Magento/PaypalGraphQl/composer.json
index 8d012be3492dd..13864e54fa1f5 100644
--- a/app/code/Magento/PaypalGraphQl/composer.json
+++ b/app/code/Magento/PaypalGraphQl/composer.json
@@ -16,7 +16,8 @@
"magento/module-store": "*"
},
"suggest": {
- "magento/module-graph-ql": "*"
+ "magento/module-graph-ql": "*",
+ "magento/module-store-graph-ql": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/PaypalGraphQl/etc/graphql/di.xml b/app/code/Magento/PaypalGraphQl/etc/graphql/di.xml
index cd5d6e2062bb9..f1b99295f5627 100644
--- a/app/code/Magento/PaypalGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/PaypalGraphQl/etc/graphql/di.xml
@@ -11,6 +11,7 @@
+
@@ -53,4 +54,12 @@
+
+
+
+
+ - payment/payflowpro_cc_vault/active
+
+
+
diff --git a/app/code/Magento/PaypalGraphQl/etc/graphql/events.xml b/app/code/Magento/PaypalGraphQl/etc/graphql/events.xml
index 41154e5ae06e6..0d2be95d77c92 100644
--- a/app/code/Magento/PaypalGraphQl/etc/graphql/events.xml
+++ b/app/code/Magento/PaypalGraphQl/etc/graphql/events.xml
@@ -12,4 +12,7 @@
+
+
+
diff --git a/app/code/Magento/PaypalGraphQl/etc/schema.graphqls b/app/code/Magento/PaypalGraphQl/etc/schema.graphqls
index b8f14eec70d18..52b0a6ec97518 100644
--- a/app/code/Magento/PaypalGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/PaypalGraphQl/etc/schema.graphqls
@@ -102,6 +102,7 @@ input PayflowProTokenInput @doc(description:"Input required to fetch payment tok
input PayflowProInput @doc(description:"Required input for Payflow Pro and Payments Pro payment methods.") {
cc_details: CreditCardDetailsInput! @doc(description: "Required input for credit card related information")
+ is_active_payment_token_enabler: Boolean @doc(description:"States whether details about the customer's credit/debit card should be tokenized for later usage. Required only if Vault is enabled for PayPal Payflow Pro payment integration.")
}
input CreditCardDetailsInput @doc(description:"Required fields for Payflow Pro and Payments Pro credit card payments") {
@@ -141,3 +142,7 @@ input PayflowProResponseInput @doc(description:"Input required to complete payme
type PayflowProResponseOutput {
cart: Cart!
}
+
+type StoreConfig {
+ payment_payflowpro_cc_vault_active: String @doc(description: "Payflow Pro vault status.")
+}
diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Customer/SaveCartDataWithPayflowProTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Customer/SaveCartDataWithPayflowProTest.php
new file mode 100644
index 0000000000000..11bd306211b9a
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Customer/SaveCartDataWithPayflowProTest.php
@@ -0,0 +1,258 @@
+json = $this->objectManager->get(SerializerInterface::class);
+ $this->quoteIdToMaskedId = $this->objectManager->get(QuoteIdToMaskedQuoteId::class);
+ }
+
+ /**
+ * Place order use payflowpro method and save cart data to future
+ *
+ * @magentoDataFixture Magento/Sales/_files/default_rollback.php
+ * @magentoDataFixture Magento/Sales/_files/default_rollback.php
+ * @magentoDataFixture Magento/Customer/_files/customer.php
+ * @magentoDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php
+ *
+ * @return void
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testPlaceOrderAndSaveDataForFuturePayflowPro(): void
+ {
+ $responseData = $this->placeOrderPayflowPro('is_active_payment_token_enabler: true');
+ $this->assertArrayHasKey('data', $responseData);
+ $this->assertArrayHasKey('createPayflowProToken', $responseData['data']);
+ $this->assertNotEmpty($this->getVaultCartData()->getPublicHash());
+ $this->assertNotEmpty($this->getVaultCartData()->getTokenDetails());
+ $this->assertNotEmpty($this->getVaultCartData()->getGatewayToken());
+ $this->assertTrue($this->getVaultCartData()->getIsActive());
+ $this->assertTrue($this->getVaultCartData()->getIsVisible());
+ }
+
+ /**
+ * Place order use payflowpro method and not save cart data to future
+ *
+ * @magentoDataFixture Magento/Sales/_files/default_rollback.php
+ * @magentoDataFixture Magento/Sales/_files/default_rollback.php
+ * @magentoDataFixture Magento/Customer/_files/customer.php
+ * @magentoDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
+ * @magentoDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php
+ *
+ * @return void
+ */
+ public function testPlaceOrderAndNotSaveDataForFuturePayflowPro(): void
+ {
+ $responseData = $this->placeOrderPayflowPro('is_active_payment_token_enabler: false');
+ $this->assertArrayHasKey('data', $responseData);
+ $this->assertArrayHasKey('createPayflowProToken', $responseData['data']);
+ $this->assertNotEmpty($this->getVaultCartData()->getPublicHash());
+ $this->assertNotEmpty($this->getVaultCartData()->getTokenDetails());
+ $this->assertNotEmpty($this->getVaultCartData()->getGatewayToken());
+ $this->assertTrue($this->getVaultCartData()->getIsActive());
+ $this->assertFalse($this->getVaultCartData()->getIsVisible());
+ }
+
+ /**
+ * @param $isActivePaymentTokenEnabler
+ *
+ * @return array
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ private function placeOrderPayflowPro($isActivePaymentTokenEnabler)
+ {
+ $paymentMethod = 'payflowpro';
+ $this->enablePaymentMethod($paymentMethod);
+ $this->enablePaymentMethod('payflowpro_cc_vault');
+ $reservedQuoteId = 'test_quote';
+
+ $payload = 'BILLTOCITY=CityM&AMT=0.00&BILLTOSTREET=Green+str,+67&VISACARDLEVEL=12&SHIPTOCITY=CityM'
+ . '&NAMETOSHIP=John+Smith&ZIP=75477&BILLTOLASTNAME=Smith&BILLTOFIRSTNAME=John'
+ . '&RESPMSG=Verified&PROCCVV2=M&STATETOSHIP=AL&NAME=John+Smith&BILLTOZIP=75477&CVV2MATCH=Y'
+ . '&PNREF=B70CCC236815&ZIPTOSHIP=75477&SHIPTOCOUNTRY=US&SHIPTOSTREET=Green+str,+67&CITY=CityM'
+ . '&HOSTCODE=A&LASTNAME=Smith&STATE=AL&SECURETOKEN=MYSECURETOKEN&CITYTOSHIP=CityM&COUNTRYTOSHIP=US'
+ . '&AVSDATA=YNY&ACCT=1111&AUTHCODE=111PNI&FIRSTNAME=John&RESULT=0&IAVS=N&POSTFPSMSG=No+Rules+Triggered&'
+ . 'BILLTOSTATE=AL&BILLTOCOUNTRY=US&EXPDATE=0222&CARDTYPE=0&PREFPSMSG=No+Rules+Triggered&SHIPTOZIP=75477&'
+ . 'PROCAVS=A&COUNTRY=US&AVSZIP=N&ADDRESS=Green+str,+67&BILLTONAME=John+Smith&'
+ . 'ADDRESSTOSHIP=Green+str,+67&'
+ . 'AVSADDR=Y&SECURETOKENID=MYSECURETOKENID&SHIPTOSTATE=AL&TRANSTIME=2019-06-24+07%3A53%3A10';
+
+ $cart = $this->getQuoteByReservedOrderId($reservedQuoteId);
+ $cartId = $this->quoteIdToMaskedId->execute((int)$cart->getId());
+
+ $query = <<objectManager->create(Token::class);
+ $customerToken = $tokenModel->createCustomerToken(1)->getToken();
+
+ $requestHeaders = [
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer ' . $customerToken
+ ];
+ $paypalResponse = new DataObject(
+ [
+ 'result' => '0',
+ 'securetoken' => 'mysecuretoken',
+ 'securetokenid' => 'mysecuretokenid',
+ 'respmsg' => 'Approved',
+ 'result_code' => '0',
+ ]
+ );
+
+ $this->gatewayMock
+ ->method('postRequest')
+ ->willReturn($paypalResponse);
+
+ $this->gatewayMock
+ ->method('postRequest')
+ ->willReturn(
+ new DataObject(
+ [
+ 'result' => '0',
+ 'pnref' => 'A70AAC2378BA',
+ 'respmsg' => 'Approved',
+ 'authcode' => '647PNI',
+ 'avsaddr' => 'Y',
+ 'avszip' => 'N',
+ 'hostcode' => 'A',
+ 'procavs' => 'A',
+ 'visacardlevel' => '12',
+ 'transtime' => '2019-06-24 10:12:03',
+ 'firstname' => 'Cristian',
+ 'lastname' => 'Partica',
+ 'amt' => '14.99',
+ 'acct' => '1111',
+ 'expdate' => '0221',
+ 'cardtype' => '0',
+ 'iavs' => 'N',
+ 'result_code' => '0',
+ ]
+ )
+ );
+
+ $response = $this->graphQlRequest->send($query, [], '', $requestHeaders);
+
+ return $this->json->unserialize($response->getContent());
+ }
+
+ /**
+ * Get saved cart data
+ *
+ * @return PaymentTokenInterface
+ */
+ private function getVaultCartData()
+ {
+ /** @var PaymentTokenManagement $tokenManagement */
+ $tokenManagement = $this->objectManager->get(PaymentTokenManagement::class);
+ $token = $tokenManagement->getByGatewayToken(
+ 'B70CCC236815',
+ 'payflowpro',
+ 1
+ );
+ /** @var PaymentTokenRepository $tokenRepository */
+ $tokenRepository = $this->objectManager->get(PaymentTokenRepository::class);
+ return $tokenRepository->getById($token->getEntityId());
+ }
+}