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()); + } +}