From 61f70f6385c0d2674132a231f72544d712602fe0 Mon Sep 17 00:00:00 2001 From: Jeantwan Teuma Date: Wed, 5 Oct 2022 10:08:26 +0200 Subject: [PATCH] Recurring payments added functionality (#1729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PW-6764 - Delete TransactionAuthorization class and start using checkout to process payments using vault tokens * PW-6764 - Create new AdyenPaymentMethod model, repository and data patch * PW-6764 - Move function to build payment method recurring data to vault helper. Also create ResourceModel * PW-6764 - Add try catch block to function in Vault * PW-6764 - Save paypal token in vault table * PW-6764 - Create new exception. Add creation logic to CheckoutPaymentsDetailsHandler. Move validation in the getVaultPaymentMethodToken function * PW-6764 - Do not force exception * PW-6764 - Create function to check if recurringDetailReference is included in response * PW-6764 - Update AdyenHppObserver to add string in additionalData to ensure that token is visible, IF it is eventually created * PW-6764 - Setup functionality to display a payment method in the checkout page * PW-6764 - Create new paymentMethodVault which will be used to ensure that for hpp tokens, no initialization is required * PW-6764 - Create PayPal tokens w/type=Account and the brand in the details column * PW-6764 - Update PaymentMethodRenderer * PW-6764 - Add exception handling around createAdyenPaymentMethod function * PW-6764 - Create new vault data builder to be used for hpp methods. Also save the tokenType when it is created and then re-use it when token is to be re-used * PW-6764 - Add functionality to only CardOnFile payment method tokens, in checkout * PW-6764 - Add instantPurchase functionality to Payment Methods * PW-6764 - Add SEPA * PW-6764 - Update admin tooltip. Rename functions in Vault helper to make it clear which one is used for what * PW-6764 - Update label shown in checkout page * PW-6764 - Run upgrade scripts on newer version * PW-6764 - Create new observer which will call Adyen to disable a token, after it is disabled on magento side * PW-6764 - Add exception handling to disable call * PW-6764 - Update shopper template * PW-6764 - Add function to check if provider is a recurring one and use it to send the shopper interaction * PW-6764 - Split enableRecurring in payment methods to enableSubscription And enableCardOnFile. Use either, depending on what token type is in use * PW-6790 - Move functionality to get the creditcard types to the paymentMethods Helper. Also create new payment method class for googlepay * PW-6790 - Create new parent uiComponentProvider to be able to render the card provider even in the hpp provider (used for googlepay, applepay) * PW-6790 - Render card tokens created with googlepay in the card section, on the user page * PW-6790 - Remove useless methods from PaymentMethodInterface * PW-6790 - Add token handling in the PaymentResponseHandler and update vault to not need the object payment method * PW-6790 - Add isWallet function and use it for GooglePay * PW-6790 - Add Apple pay classes * PW-6790 - Create Abstract token formatter for instantPurchase, to show card formatting if a wallet payment (googlepay) was used * PW-6790 - Update admin area tooltip * PW-6790 - Remove unnecessary dependencies * PW-6790 - Remove RecurringPmVaultDataBuilder and use the normal vault data builder instead * PW-6790 - Remove SEPA related code that was previously unused * [PW-6858] Add multiselect config for selecting which PMs to tokenize (#1605) * [PW-6858] add config for selecting payment methods that should be tokenized * [PW-6858] remove adyenpaymentmethods patch script * [PW-6858] update property declaration syntax to support php 7.3 * Add UnscheduledCardOnFile for alternative payment methods (#1612) * [PW-6910] Add functionality to tokenize AmazonPay payments (#1621) * Add amazon pay payment method * Add the payment method from statedata * Update statedata property name as there is another statedata in the same class * Add get variant method to statedata helper * [PW-6974] - Handle tokenizations on wallet methods with the correct txVariant (#1668) * PW-6974 - Refactor payment method classes by creating a TxVariant class and an AbstractWalletPaymentMethod * PW-6974 - Move functionality from CheckoutPaymentsDetailsHandler to VaultDetailsHandler. Update Vault function to save card type and wallet type * PW-6974 - Update paymentMethod factory * PW-6974 - Remove isWalletPaymentMethod function and instead check the instance of the object * PW-6974 - Add comment to admin interface * PW-6974 - Move IS_ACTIVE flag to vault handler. Also do not allow ApplePay to be selected for now, since it cannot be tested yet * PW-6974 - Update comment * PW-6974 - Remove SEPA specific field when handling webhook * PW-6974 - Add signature to files * [PW-6785] - Coverage report for SonarCloud (#1665) * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Coverage report for SonarCloud * [PW-6785] - Refactor phpunit.xml for configuration and main.yml for pipeline * [PW-6785] - Add sonar-project.properties file * [PW-6785] - Remove sonar analysis from CI * [PW-6785] - Remove unnecessary dependencies * [PW-6785] - Coverage test function * [PW-6785] - Enable CI based sonar analysis * [PW-6785] - Code formatting * [PW-6785] - Temporarily disable PHP8.1 matrix * [PW-6785] - Change allowed list for scan * [PW-6785] - Change allowed list for scan * [PW-6785] - Exclude vendor directory from sonar analysis * [PW-6785] - Remove the filter for sonar analysis and include all the project files * [PW-6785] - Revert commits 6f6ca5d and bd74dd3 * [PW-7021] - Add failure counter for pay by link attempts (#1667) * [PW-7021] - Add failure counter for pay by link attempts * Update Helper/Webhook/AuthorisationWebhookHandler.php Co-authored-by: Michael Paul * Update Helper/Webhook/AuthorisationWebhookHandler.php Co-authored-by: Michael Paul * [PW-7021] - Code formatting Co-authored-by: Michael Paul * Revert "[PW-6785] - Coverage report for SonarCloud (#1665)" (#1671) This reverts commit f053b6f93fa900197ff751d0db04242688d15ae6. * Fix issue where CC vault tokens were not being created * [PW-7100] Add CardOnFile and UnshceduledCardOnFile to Vault Card (#1693) * update recurring_card_token_type in adyen_oneclick to show all contract types if vault is selected * abstract recurring type value passed to buildCardRecurringData(), save tokenType in details field * update TokenUiComponentProvider to display token if condition is met * cleanup comment in the header * add new template an js file for vault * add interceptor that returns filtered customer session tokens * remove banner Co-authored-by: Michael Paul * remove comment * update unit tests * use different function for checking whether the value for tokenType is set Co-authored-by: Jeantwan Teuma * update tooltip for adyen_oneclick Co-authored-by: Jeantwan Teuma * update return type of afterGetCustomerSessionTokens function Co-authored-by: Michael Paul Co-authored-by: Jeantwan Teuma * [PW-7179] - Adjust vault tokenization functionality (#1709) * PW-7179 - Re-add parent AdyenUiComponentProvider * PW-7179 - Check if property exists before checking type * PW-7179 - Fix issue created in merge commit and update PaymentVaultDeleteToken * PW-7179 - Do not show ApplePay and AmazonPay for now. Also add to do annotations for them * [PW-7157]Add India live prefix for frontend (#1708) * Add India live prefix for frontend * revert adyen-methods.js Co-authored-by: Jeantwan Teuma * PW-7179 - Move tooltip to comment * PW-7129: Remove full request/response logging (#1712) * PW-7129: Remove full request/response logging Add new log processors to help with troubleshooting. * PW-7129: Use context placeholders * Adding number format to getAmountIncludingTax in test (#1715) * Adding number format to getAmountIncludingTax in test * Apply number format on getTaxAmount Co-authored-by: Alexandros Moraitis Co-authored-by: Michael Paul Co-authored-by: Ángel Campos * recurring-changes - Remove unused paymentMethodsHelper * Remove duplicate AdyenPaymentHppFacade * Update formatting due to code smells * More updates related to code smells Co-authored-by: Peter Ojo Co-authored-by: Alexandros Moraitis Co-authored-by: Can Demiralp Co-authored-by: Michael Paul Co-authored-by: Rok Popov Ledinski Co-authored-by: Ángel Campos --- Block/Customer/CardRenderer.php | 8 +- Block/Customer/PaymentMethodRenderer.php | 74 ++++++ Block/Form/Cc.php | 26 +-- Controller/Process/Result.php | 4 +- Exception/InvalidAdditionalDataException.php | 18 ++ Exception/PaymentMethodException.php | 18 ++ .../Http/Client/TransactionAuthorization.php | 65 ------ Gateway/Request/RecurringDataBuilder.php | 18 +- Gateway/Request/RecurringVaultDataBuilder.php | 20 +- .../Request/ShopperInteractionDataBuilder.php | 13 +- .../CheckoutPaymentsDetailsHandler.php | 34 +-- Gateway/Response/VaultDetailsHandler.php | 98 +++++++- Helper/Config.php | 5 + Helper/PaymentMethods.php | 66 ++++-- .../AbstractWalletPaymentMethod.php | 48 ++++ .../PaymentMethods/AmazonPayPaymentMethod.php | 55 +++++ .../PaymentMethods/ApplePayPaymentMethod.php | 54 +++++ .../PaymentMethods/GooglePayPaymentMethod.php | 53 +++++ Helper/PaymentMethods/PayPalPaymentMethod.php | 53 +++++ .../PaymentMethods/PaymentMethodFactory.php | 51 +++++ .../PaymentMethods/PaymentMethodInterface.php | 30 +++ Helper/PaymentMethods/SepaPaymentMethod.php | 58 +++++ Helper/PaymentMethods/TxVariant.php | 58 +++++ Helper/PaymentResponseHandler.php | 69 +++++- Helper/Recurring.php | 9 +- Helper/Requests.php | 34 +-- Helper/StateData.php | 11 + Helper/Vault.php | 215 +++++++++++++++++- .../RecurringContractWebhookHandler.php | 2 - Model/Billing/Agreement.php | 40 ---- Model/Comment/ApiKeyEnding.php | 10 + Model/Comment/TokenizedPaymentMethods.php | 28 +++ .../Config/Source/TokenizedPaymentMethods.php | 39 ++++ .../AbstractAdyenTokenFormatter.php | 94 ++++++++ .../CreditCard/TokenFormatter.php | 78 +------ .../PaymentMethods/AvailabilityChecker.php | 41 ++++ .../PaymentMethods/TokenFormatter.php | 47 ++++ Model/Method/PaymentMethodVault.php | 81 +++++++ Model/Ui/AdyenCcConfigProvider.php | 120 ++++------ Model/Ui/AdyenHppConfigProvider.php | 2 + Model/Ui/AdyenOneclickConfigProvider.php | 32 +-- Model/Ui/AdyenUiComponentProvider.php | 60 +++++ Model/Ui/PaymentMethodUiComponentProvider.php | 85 +++++++ Model/Ui/TokenUiComponentProvider.php | 44 +--- Observer/AdyenHppDataAssignObserver.php | 39 +--- Observer/VaultDeleteTokenObserver.php | 108 +++++++++ Plugin/CustomerFilterVaultTokens.php | 48 ++++ Plugin/PaymentVaultDeleteToken.php | 30 ++- Test/Unit/Helper/RequestsTest.php | 6 +- etc/adminhtml/system/adyen_hpp.xml | 15 +- etc/adminhtml/system/adyen_oneclick.xml | 12 +- etc/config.xml | 10 + etc/di.xml | 76 ++++++- etc/events.xml | 3 + etc/frontend/di.xml | 1 + .../layout/vault_cards_listaction.xml | 6 +- .../customer_account/payment_method.phtml | 37 +++ .../method-renderer/adyen-vault-method.js | 66 ++++++ .../method-renderer/payment_method_vault.js | 66 ++++++ .../payment/payment-method-vault-form.html | 45 ++++ .../payment/payment-method-vault.html | 39 ++++ 61 files changed, 2157 insertions(+), 518 deletions(-) create mode 100644 Block/Customer/PaymentMethodRenderer.php create mode 100644 Exception/InvalidAdditionalDataException.php create mode 100644 Exception/PaymentMethodException.php delete mode 100644 Gateway/Http/Client/TransactionAuthorization.php create mode 100644 Helper/PaymentMethods/AbstractWalletPaymentMethod.php create mode 100644 Helper/PaymentMethods/AmazonPayPaymentMethod.php create mode 100644 Helper/PaymentMethods/ApplePayPaymentMethod.php create mode 100644 Helper/PaymentMethods/GooglePayPaymentMethod.php create mode 100644 Helper/PaymentMethods/PayPalPaymentMethod.php create mode 100644 Helper/PaymentMethods/PaymentMethodFactory.php create mode 100644 Helper/PaymentMethods/PaymentMethodInterface.php create mode 100644 Helper/PaymentMethods/SepaPaymentMethod.php create mode 100644 Helper/PaymentMethods/TxVariant.php create mode 100644 Model/Comment/TokenizedPaymentMethods.php create mode 100644 Model/Config/Source/TokenizedPaymentMethods.php create mode 100644 Model/InstantPurchase/AbstractAdyenTokenFormatter.php create mode 100644 Model/InstantPurchase/PaymentMethods/AvailabilityChecker.php create mode 100644 Model/InstantPurchase/PaymentMethods/TokenFormatter.php create mode 100644 Model/Method/PaymentMethodVault.php create mode 100644 Model/Ui/AdyenUiComponentProvider.php create mode 100644 Model/Ui/PaymentMethodUiComponentProvider.php create mode 100644 Observer/VaultDeleteTokenObserver.php create mode 100644 Plugin/CustomerFilterVaultTokens.php create mode 100644 view/frontend/templates/customer_account/payment_method.phtml create mode 100644 view/frontend/web/js/view/payment/method-renderer/adyen-vault-method.js create mode 100644 view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js create mode 100644 view/frontend/web/template/payment/payment-method-vault-form.html create mode 100644 view/frontend/web/template/payment/payment-method-vault.html diff --git a/Block/Customer/CardRenderer.php b/Block/Customer/CardRenderer.php index b6236e95c..0ba4560e4 100644 --- a/Block/Customer/CardRenderer.php +++ b/Block/Customer/CardRenderer.php @@ -37,14 +37,16 @@ public function __construct( } /** - * Can render specified token + * Returns true if methodCode = adyen_cc OR (methodCode = adyen_hpp AND maskedCC exists in details. For googlepay) * * @param PaymentTokenInterface $token * @return boolean */ - public function canRender(PaymentTokenInterface $token) + public function canRender(PaymentTokenInterface $token): bool { - return $token->getPaymentMethodCode() === AdyenCcConfigProvider::CODE; + $details = json_decode($token->getTokenDetails() ?: '{}', true); + return $token->getPaymentMethodCode() === AdyenCcConfigProvider::CODE || + ($token->getPaymentMethodCode() === AdyenHppConfigProvider::CODE && array_key_exists('maskedCC', $details)); } /** diff --git a/Block/Customer/PaymentMethodRenderer.php b/Block/Customer/PaymentMethodRenderer.php new file mode 100644 index 000000000..0ba7d92ca --- /dev/null +++ b/Block/Customer/PaymentMethodRenderer.php @@ -0,0 +1,74 @@ + + */ + +namespace Adyen\Payment\Block\Customer; + +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodInterface; +use Adyen\Payment\Model\Ui\AdyenHppConfigProvider; +use Magento\Framework\View\Element\Template\Context; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Block\AbstractTokenRenderer; + +class PaymentMethodRenderer extends AbstractTokenRenderer +{ + /** @var Data */ + private $dataHelper; + + /** @var PaymentMethodFactory */ + private $paymentMethodFactory; + + + public function __construct( + Context $context, + Data $dataHelper, + PaymentMethodFactory $paymentMethodFactory, + array $data = [] + ) { + parent::__construct($context, $data); + $this->dataHelper = $dataHelper; + $this->paymentMethodFactory = $paymentMethodFactory; + } + + public function getText(): string + { + try { + $paymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($this->getTokenDetails()['type']); + $text = $paymentMethod->getPaymentMethodName(); + } catch (PaymentMethodException $exception) { + $text = ''; + } + + return $text; + } + + public function getIconUrl(): string + { + return $this->dataHelper->getVariantIcon($this->getTokenDetails()['type'])['url']; + } + + public function getIconHeight(): int + { + return $this->dataHelper->getVariantIcon($this->getTokenDetails()['type'])['height']; + } + + public function getIconWidth(): int + { + return $this->dataHelper->getVariantIcon($this->getTokenDetails()['type'])['width']; + } + + public function canRender(PaymentTokenInterface $token): bool + { + return $token->getPaymentMethodCode() === AdyenHppConfigProvider::CODE; + } +} diff --git a/Block/Form/Cc.php b/Block/Form/Cc.php index 64a1c2578..7ae0746a4 100755 --- a/Block/Form/Cc.php +++ b/Block/Form/Cc.php @@ -15,6 +15,7 @@ use Adyen\Payment\Helper\Config; use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\Installments; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\Recurring; use Adyen\Payment\Helper\Vault; use Adyen\Payment\Logger\AdyenLogger; @@ -80,6 +81,9 @@ class Cc extends \Magento\Payment\Block\Form\Cc */ private $vaultHelper; + /** @var PaymentMethods */ + private $paymentMethodsHelper; + /** * Cc constructor. * @@ -108,6 +112,7 @@ public function __construct( Config $configHelper, Session $customerSession, Vault $vaultHelper, + PaymentMethods $paymentMethodsHelper, array $data = [] ) { parent::__construct($context, $paymentConfig); @@ -121,6 +126,7 @@ public function __construct( $this->configHelper = $configHelper; $this->customerSession = $customerSession; $this->vaultHelper = $vaultHelper; + $this->paymentMethodsHelper = $paymentMethodsHelper; } /** @@ -163,26 +169,14 @@ public function getLocale() } /** - * Retrieve available credit card type codes by alt code + * Retrieve available credit card type codes by alt code. Function is required so that it can be called + * from block form * * @return array */ - public function getCcAvailableTypesByAlt() + public function getCcAvailableTypesByAlt(): array { - $types = []; - $ccTypes = $this->adyenHelper->getAdyenCcTypes(); - - $availableTypes = $this->adyenHelper->getAdyenCcConfigData('cctypes'); - if ($availableTypes) { - $availableTypes = explode(',', $availableTypes); - foreach (array_keys($ccTypes) as $code) { - if (in_array($code, $availableTypes)) { - $types[$ccTypes[$code]['code_alt']] = $code; - } - } - } - - return $types; + return $this->paymentMethodsHelper->getCcAvailableTypesByAlt(); } /** diff --git a/Controller/Process/Result.php b/Controller/Process/Result.php index 0e5da8d45..e6a6c86f0 100755 --- a/Controller/Process/Result.php +++ b/Controller/Process/Result.php @@ -268,10 +268,10 @@ protected function validateResponse($response) if (!empty($response['additionalData']['recurring.recurringDetailReference']) && $this->payment->getMethodInstance()->getCode() !== \Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider::CODE) { if ($this->vaultHelper->isCardVaultEnabled()) { - $this->vaultHelper->saveRecurringDetails($this->payment, $response['additionalData']); + $this->vaultHelper->saveRecurringCardDetails($this->payment, $response['additionalData']); } else { $order = $this->payment->getOrder(); - $this->recurringHelper->createAdyenBillingAgreement($order, $response['additionalData'], $this->payment->getAdditionalInformation()); + $this->recurringHelper->createAdyenBillingAgreement($order, $response['additionalData']); } $this->orderResourceModel->save($order); } diff --git a/Exception/InvalidAdditionalDataException.php b/Exception/InvalidAdditionalDataException.php new file mode 100644 index 000000000..0f2fbe6f9 --- /dev/null +++ b/Exception/InvalidAdditionalDataException.php @@ -0,0 +1,18 @@ + + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +namespace Adyen\Payment\Exception; + +class InvalidAdditionalDataException extends AbstractAdyenException +{ + +} diff --git a/Exception/PaymentMethodException.php b/Exception/PaymentMethodException.php new file mode 100644 index 000000000..dfc809690 --- /dev/null +++ b/Exception/PaymentMethodException.php @@ -0,0 +1,18 @@ + + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +namespace Adyen\Payment\Exception; + +class PaymentMethodException extends AbstractAdyenException +{ + +} diff --git a/Gateway/Http/Client/TransactionAuthorization.php b/Gateway/Http/Client/TransactionAuthorization.php deleted file mode 100644 index 4be92a382..000000000 --- a/Gateway/Http/Client/TransactionAuthorization.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ - -namespace Adyen\Payment\Gateway\Http\Client; - -use Magento\Payment\Gateway\Http\ClientInterface; -use Adyen\Payment\Model\ApplicationInfo; - -class TransactionAuthorization implements ClientInterface -{ - - /** - * @var \Adyen\Client - */ - protected $client; - - /** - * @var ApplicationInfo - */ - private $applicationInfo; - - /** - * TransactionAuthorization constructor. - * @param \Adyen\Payment\Helper\Data $adyenHelper - * @param ApplicationInfo $applicationInfo - * @throws \Adyen\AdyenException - */ - public function __construct( - \Adyen\Payment\Helper\Data $adyenHelper, - \Adyen\Payment\Model\ApplicationInfo $applicationInfo - ) { - $this->applicationInfo = $applicationInfo; - $this->client = $adyenHelper->initializeAdyenClient(); - } - - /** - * @param \Magento\Payment\Gateway\Http\TransferInterface $transferObject - * @return array|mixed - * @throws \Adyen\AdyenException - */ - public function placeRequest(\Magento\Payment\Gateway\Http\TransferInterface $transferObject) - { - $request = $transferObject->getBody(); - $requestOptions = []; - - // call lib - $service = new \Adyen\Service\Payment($this->client); - - try { - $response = $service->authorise($request, $requestOptions); - } catch (\Adyen\AdyenException $e) { - $response['error'] = $e->getMessage(); - } - - return $response; - } -} diff --git a/Gateway/Request/RecurringDataBuilder.php b/Gateway/Request/RecurringDataBuilder.php index 05647ed88..0b3096bae 100644 --- a/Gateway/Request/RecurringDataBuilder.php +++ b/Gateway/Request/RecurringDataBuilder.php @@ -13,6 +13,8 @@ use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\Requests; +use Adyen\Payment\Helper\Vault; +use Adyen\Payment\Helper\StateData; use Adyen\Payment\Logger\AdyenLogger; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; @@ -25,12 +27,22 @@ class RecurringDataBuilder implements BuilderInterface /** @var AdyenLogger */ private $adyenLogger; + /** @var Vault */ + private $vaultHelper; + + /** @var StateData */ + private $stateData; + public function __construct( Requests $adyenRequestsHelper, - AdyenLogger $adyenLogger + AdyenLogger $adyenLogger, + Vault $vaultHelper, + StateData $stateData ) { $this->adyenRequestsHelper = $adyenRequestsHelper; $this->adyenLogger = $adyenLogger; + $this->vaultHelper = $vaultHelper; + $this->stateData = $stateData; } /** @@ -46,11 +58,11 @@ public function build(array $buildSubject): array $order = $payment->getOrder(); $storeId = $order->getStoreId(); $method = $payment->getMethod(); - + $brand = $this->stateData->getPaymentMethodVariant($order->getQuoteId()); if ($method === PaymentMethods::ADYEN_CC) { $body = $this->adyenRequestsHelper->buildCardRecurringData($storeId, $payment); } elseif ($method === PaymentMethods::ADYEN_HPP) { - $body = $this->adyenRequestsHelper->buildAlternativePaymentRecurringData($storeId, $payment); + $body = $this->vaultHelper->buildPaymentMethodRecurringData($storeId, $brand); } elseif ($method === PaymentMethods::ADYEN_ONE_CLICK) { $body = $this->adyenRequestsHelper->buildAdyenTokenizedPaymentRecurringData($storeId, $payment); } else { diff --git a/Gateway/Request/RecurringVaultDataBuilder.php b/Gateway/Request/RecurringVaultDataBuilder.php index 72a4830dc..ccb2216f6 100644 --- a/Gateway/Request/RecurringVaultDataBuilder.php +++ b/Gateway/Request/RecurringVaultDataBuilder.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2019 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2022 Adyen BV (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -11,6 +11,9 @@ namespace Adyen\Payment\Gateway\Request; +use Adyen\Payment\Helper\Vault; +use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Request\BuilderInterface; class RecurringVaultDataBuilder implements BuilderInterface @@ -22,15 +25,20 @@ class RecurringVaultDataBuilder implements BuilderInterface public function build(array $buildSubject) { $requestBody = []; - $recurring = ['contract' => \Adyen\Payment\Model\RecurringType::RECURRING]; - $requestBody['recurring'] = $recurring; - /** @var \Magento\Payment\Gateway\Data\PaymentDataObject $paymentDataObject */ - $paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($buildSubject); + /** @var PaymentDataObject $paymentDataObject */ + $paymentDataObject = SubjectReader::readPayment($buildSubject); $payment = $paymentDataObject->getPayment(); $extensionAttributes = $payment->getExtensionAttributes(); $paymentToken = $extensionAttributes->getVaultPaymentToken(); + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); - $requestBody['selectedRecurringDetailReference'] = $paymentToken->getGatewayToken(); + // Add paymentMethod object in array, since currently checkout component is not loaded for vault payment methods + $requestBody['paymentMethod'] = ['storedPaymentMethodId' => $paymentToken->getGatewayToken()]; + + // For now this will only be used by tokens created trough adyen_hpp payment methods + if (array_key_exists(Vault::TOKEN_TYPE, $details)) { + $requestBody['recurringProcessingModel'] = $details[Vault::TOKEN_TYPE]; + } $request['body'] = $requestBody; diff --git a/Gateway/Request/ShopperInteractionDataBuilder.php b/Gateway/Request/ShopperInteractionDataBuilder.php index 35993bdbc..c8f6def76 100644 --- a/Gateway/Request/ShopperInteractionDataBuilder.php +++ b/Gateway/Request/ShopperInteractionDataBuilder.php @@ -15,6 +15,7 @@ use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; use Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider; use Adyen\Payment\Model\Ui\AdyenPayByLinkConfigProvider; +use Magento\Framework\App\State; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\Context; use Magento\Payment\Gateway\Helper\SubjectReader; @@ -27,18 +28,12 @@ class ShopperInteractionDataBuilder implements BuilderInterface const SHOPPER_INTERACTION_ECOMMERCE = 'Ecommerce'; /** - * @var \Magento\Framework\App\State + * @var State */ private $appState; - /** - * RecurringDataBuilder constructor. - * - * @param Context $context - */ - public function __construct( - Context $context - ) { + public function __construct(Context $context) + { $this->appState = $context->getAppState(); } diff --git a/Gateway/Response/CheckoutPaymentsDetailsHandler.php b/Gateway/Response/CheckoutPaymentsDetailsHandler.php index d91be5fdc..eb493bcfe 100644 --- a/Gateway/Response/CheckoutPaymentsDetailsHandler.php +++ b/Gateway/Response/CheckoutPaymentsDetailsHandler.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2015 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2022 Adyen BV (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -12,41 +12,31 @@ namespace Adyen\Payment\Gateway\Response; use Adyen\Payment\Helper\Data; -use Adyen\Payment\Helper\Recurring; -use Adyen\Payment\Helper\Vault; +use Adyen\Payment\Model\Ui\AdyenBoletoConfigProvider; +use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; class CheckoutPaymentsDetailsHandler implements HandlerInterface { /** @var Data */ protected $adyenHelper; - /** @var Recurring */ - private $recurringHelper; - - /** @var Vault */ - private $vaultHelper; - public function __construct( - Data $adyenHelper, - Recurring $recurringHelper, - Vault $vaultHelper + Data $adyenHelper ) { $this->adyenHelper = $adyenHelper; - $this->recurringHelper = $recurringHelper; - $this->vaultHelper = $vaultHelper; } /** * This is being used for all checkout methods (adyen hpp payment method) * - * @param array $handlingSubject - * @param array $response */ public function handle(array $handlingSubject, array $response) { - $paymentDataObject = \Magento\Payment\Gateway\Helper\SubjectReader::readPayment($handlingSubject); + $paymentDataObject = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ $payment = $paymentDataObject->getPayment(); // set transaction not to processing by default wait for notification @@ -54,7 +44,7 @@ public function handle(array $handlingSubject, array $response) // Email sending is set at CheckoutDataBuilder for Boleto // Otherwise, we don't want to send a confirmation email - if ($payment->getMethod() != \Adyen\Payment\Model\Ui\AdyenBoletoConfigProvider::CODE) { + if ($payment->getMethod() != AdyenBoletoConfigProvider::CODE) { $payment->getOrder()->setCanSendNewEmailFlag(false); } @@ -67,14 +57,6 @@ public function handle(array $handlingSubject, array $response) $payment->setTransactionId($response['pspReference']); } - if (!empty($response['additionalData']['recurring.recurringDetailReference']) && - !$this->vaultHelper->isCardVaultEnabled() && - $payment->getMethodInstance()->getCode() !== \Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider::CODE - ) { - $order = $payment->getOrder(); - $this->recurringHelper->createAdyenBillingAgreement($order, $response['additionalData'], $payment->getAdditionalInformation()); - } - // do not close transaction so you can do a cancel() and void $payment->setIsTransactionClosed(false); $payment->setShouldCloseParentTransaction(false); diff --git a/Gateway/Response/VaultDetailsHandler.php b/Gateway/Response/VaultDetailsHandler.php index bafed3321..1babf0274 100644 --- a/Gateway/Response/VaultDetailsHandler.php +++ b/Gateway/Response/VaultDetailsHandler.php @@ -11,30 +11,57 @@ namespace Adyen\Payment\Gateway\Response; +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Helper\PaymentMethods\AbstractWalletPaymentMethod; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; +use Adyen\Payment\Helper\Recurring; use Adyen\Payment\Helper\Vault; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; +use Adyen\Payment\Model\Ui\AdyenHppConfigProvider; +use Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider; +use Magento\Framework\Exception\LocalizedException; use Magento\Payment\Gateway\Data\PaymentDataObject; use Magento\Payment\Gateway\Helper\SubjectReader; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Vault\Model\Ui\VaultConfigProvider; class VaultDetailsHandler implements HandlerInterface { - /** - * @var Vault - */ + /** @var Vault */ private $vaultHelper; - /** - * VaultDetailsHandler constructor. - * - * @param Vault $vaultHelper - */ - public function __construct(Vault $vaultHelper) - { + /** @var PaymentMethodFactory */ + private $paymentMethodFactory; + + /** @var Config */ + private $configHelper; + + /** @var AdyenLogger */ + private $adyenLogger; + + /** @var Recurring */ + private $recurringHelper; + + public function __construct( + Vault $vaultHelper, + PaymentMethodFactory $paymentMethodFactory, + Config $configHelper, + AdyenLogger $adyenLogger, + Recurring $recurringHelper + ) { $this->vaultHelper = $vaultHelper; + $this->paymentMethodFactory = $paymentMethodFactory; + $this->configHelper = $configHelper; + $this->adyenLogger = $adyenLogger; + $this->recurringHelper = $recurringHelper; } /** * @inheritdoc + * @throws PaymentMethodException|LocalizedException */ public function handle(array $handlingSubject, array $response) { @@ -43,9 +70,56 @@ public function handle(array $handlingSubject, array $response) } /** @var PaymentDataObject $orderPayment */ $orderPayment = SubjectReader::readPayment($handlingSubject); + /** @var Payment $payment */ + $payment = $orderPayment->getPayment(); + $paymentMethodInstance = $payment->getMethodInstance(); + + if ($this->vaultHelper->hasRecurringDetailReference($response) && + $paymentMethodInstance->getCode() !== AdyenOneclickConfigProvider::CODE + ) { + $storeId = $paymentMethodInstance->getStore(); + $paymentInstanceCode = $paymentMethodInstance->getCode(); + $storePaymentMethods = $this->configHelper->isStoreAlternativePaymentMethodEnabled($storeId); + $cardVaultEnabled = $this->vaultHelper->isCardVaultEnabled($storeId); + $adyenTokensEnabled = $this->recurringHelper->areAdyenTokensEnabled($storeId); - if ($this->vaultHelper->isCardVaultEnabled()) { - $this->vaultHelper->saveRecurringDetails($orderPayment->getPayment(), $response['additionalData']); + // If payment method is HPP and hpp config enabled + // Else if payment method is card and vault is enabled + // Else if payment method is card and vault is disabled and adyen tokens are enabled + if ($storePaymentMethods && $paymentInstanceCode === AdyenHppConfigProvider::CODE) { + $paymentMethod = $response['additionalData']['paymentMethod']; + try { + $payment->setAdditionalInformation(VaultConfigProvider::IS_ACTIVE_CODE, true); + $adyenPaymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($paymentMethod); + if ($adyenPaymentMethod instanceof AbstractWalletPaymentMethod) { + $this->vaultHelper->saveRecurringCardDetails( + $payment, + $response['additionalData'], + $adyenPaymentMethod + ); + } else { + $this->vaultHelper->saveRecurringPaymentMethodDetails($payment, $response['additionalData']); + } + } catch (PaymentMethodException $e) { + $this->adyenLogger->error(sprintf( + 'Unable to create payment method with tx variant %s in details handler', + $paymentMethod + )); + } + } elseif ($cardVaultEnabled && $paymentInstanceCode === AdyenCcConfigProvider::CODE) { + $this->vaultHelper->saveRecurringCardDetails($payment, $response['additionalData']); + } elseif ( + !$cardVaultEnabled && + $adyenTokensEnabled && + $paymentInstanceCode === AdyenCcConfigProvider::CODE + ) { + $order = $payment->getOrder(); + $this->recurringHelper->createAdyenBillingAgreement( + $order, + $response['additionalData'], + $payment->getAdditionalInformation() + ); + } } } } diff --git a/Helper/Config.php b/Helper/Config.php index 392a96bc0..913618847 100644 --- a/Helper/Config.php +++ b/Helper/Config.php @@ -452,6 +452,11 @@ public function getCardRecurringType($storeId): ?string return $this->getConfigData('card_type', self::XML_ADYEN_ONECLICK, $storeId); } + public function getTokenizedPaymentMethods($storeId) + { + return $this->getConfigData('tokenized_payment_methods', self::XML_ADYEN_HPP, $storeId); + } + public function debugLogsEnabled($storeId): bool { return $this->getConfigData('debug', self::XML_ADYEN_ABSTRACT_PREFIX, $storeId, true); diff --git a/Helper/PaymentMethods.php b/Helper/PaymentMethods.php index 6d1769bf6..dcf8aa775 100644 --- a/Helper/PaymentMethods.php +++ b/Helper/PaymentMethods.php @@ -12,6 +12,9 @@ namespace Adyen\Payment\Helper; use Adyen\AdyenException; +use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; +use Adyen\Payment\Model\Ui\AdyenHppConfigProvider; +use Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider; use Adyen\Payment\Logger\AdyenLogger; use Adyen\Payment\Model\Notification; use Adyen\Util\ManualCapture; @@ -555,25 +558,62 @@ public function checkPaymentMethod($payment, string $method): bool } /** - * Check if the passed payment method supports recurring functionality. + * Check if the passed payment method provider is a recurring one or not * - * Currently only SEPA is allowed on our Magento plugin. - * Possible future payment methods: + * @param string $provider + * @return bool + */ + public function isRecurringProvider(string $provider): bool + { + return in_array($provider, [ + AdyenCcConfigProvider::CC_VAULT_CODE, + AdyenHppConfigProvider::HPP_VAULT_CODE, + AdyenOneclickConfigProvider::CODE + ]); + } + + /** + * Retrieve available credit card types * - * 'ach','amazonpay','applepay','directdebit_GB','bcmc','dana','dankort','eps','gcash','giropay','googlepay','paywithgoogle', - * 'gopay_wallet','ideal','kakaopay','klarna','klarna_account','klarna_b2b','klarna_paynow','momo_wallet','paymaya_wallet', - * 'paypal','trustly','twint','uatp','billdesk_upi','payu_IN_upi','vipps','yandex_money','zip' + * @return array + */ + public function getCcAvailableTypes(): array + { + $types = []; + $ccTypes = $this->adyenHelper->getAdyenCcTypes(); + $availableTypes = $this->adyenHelper->getAdyenCcConfigData('cctypes'); + if ($availableTypes) { + $availableTypes = explode(',', $availableTypes); + foreach (array_keys($ccTypes) as $code) { + if (in_array($code, $availableTypes)) { + $types[$code] = $ccTypes[$code]['name']; + } + } + } + + return $types; + } + + /** + * Retrieve available credit card type codes by alt code * - * @param string $paymentMethod - * @return bool + * @return array */ - public function paymentMethodSupportsRecurring(string $paymentMethod): bool + public function getCcAvailableTypesByAlt(): array { - $paymentMethodRecurring = [ - 'sepadirectdebit', - ]; + $types = []; + $ccTypes = $this->adyenHelper->getAdyenCcTypes(); + $availableTypes = $this->adyenHelper->getAdyenCcConfigData('cctypes'); + if ($availableTypes) { + $availableTypes = explode(',', $availableTypes); + foreach (array_keys($ccTypes) as $code) { + if (in_array($code, $availableTypes)) { + $types[$ccTypes[$code]['code_alt']] = $code; + } + } + } - return in_array($paymentMethod, $paymentMethodRecurring); + return $types; } /** diff --git a/Helper/PaymentMethods/AbstractWalletPaymentMethod.php b/Helper/PaymentMethods/AbstractWalletPaymentMethod.php new file mode 100644 index 000000000..654d1c471 --- /dev/null +++ b/Helper/PaymentMethods/AbstractWalletPaymentMethod.php @@ -0,0 +1,48 @@ + + */ + +namespace Adyen\Payment\Helper\PaymentMethods; + +abstract class AbstractWalletPaymentMethod implements PaymentMethodInterface +{ + /** @var string */ + private $cardScheme; + + abstract public function getTxVariant(): string; + + abstract public function getPaymentMethodName(): string; + + abstract public function supportsCardOnFile(): bool; + + abstract public function supportsSubscription(): bool; + + abstract public function supportsManualCapture(): bool; + + abstract public function supportsAutoCapture(): bool; + + abstract public function supportsUnscheduledCardOnFile(): bool; + + public function __construct(?string $cardScheme) + { + $this->cardScheme = $cardScheme; + } + + public function getCardScheme(): string + { + return $this->cardScheme; + } + + public function setCardScheme(string $cardScheme): void + { + $this->cardScheme = $cardScheme; + } +} diff --git a/Helper/PaymentMethods/AmazonPayPaymentMethod.php b/Helper/PaymentMethods/AmazonPayPaymentMethod.php new file mode 100644 index 000000000..c469828c0 --- /dev/null +++ b/Helper/PaymentMethods/AmazonPayPaymentMethod.php @@ -0,0 +1,55 @@ + + */ + +namespace Adyen\Payment\Helper\PaymentMethods; + +/** TODO: This PM can be enabled for recurring purposes once tested */ +class AmazonPayPaymentMethod extends AbstractWalletPaymentMethod +{ + const TX_VARIANT = 'amazonpay'; + const NAME = 'Amazon Pay'; + + public function getTxVariant(): string + { + return self::TX_VARIANT; + } + + public function getPaymentMethodName(): string + { + return self::NAME; + } + + public function supportsManualCapture(): bool + { + return true; + } + + public function supportsAutoCapture(): bool + { + return true; + } + + public function supportsCardOnFile(): bool + { + return false; + } + + public function supportsSubscription(): bool + { + return false; + } + + public function supportsUnscheduledCardOnFile(): bool + { + return false; + } +} diff --git a/Helper/PaymentMethods/ApplePayPaymentMethod.php b/Helper/PaymentMethods/ApplePayPaymentMethod.php new file mode 100644 index 000000000..f02363f7a --- /dev/null +++ b/Helper/PaymentMethods/ApplePayPaymentMethod.php @@ -0,0 +1,54 @@ + + */ +namespace Adyen\Payment\Helper\PaymentMethods; + +/** TODO: This PM can be enabled for recurring purposes once tested */ +class ApplePayPaymentMethod extends AbstractWalletPaymentMethod +{ + const TX_VARIANT = 'applepay'; + const NAME = 'Apple Pay'; + + public function getTxVariant(): string + { + return self::TX_VARIANT; + } + + public function getPaymentMethodName(): string + { + return self::NAME; + } + + public function supportsManualCapture(): bool + { + return true; + } + + public function supportsAutoCapture(): bool + { + return true; + } + + public function supportsCardOnFile(): bool + { + return false; + } + + public function supportsSubscription(): bool + { + return false; + } + + public function supportsUnscheduledCardOnFile(): bool + { + return false; + } +} diff --git a/Helper/PaymentMethods/GooglePayPaymentMethod.php b/Helper/PaymentMethods/GooglePayPaymentMethod.php new file mode 100644 index 000000000..193562569 --- /dev/null +++ b/Helper/PaymentMethods/GooglePayPaymentMethod.php @@ -0,0 +1,53 @@ + + */ +namespace Adyen\Payment\Helper\PaymentMethods; + +class GooglePayPaymentMethod extends AbstractWalletPaymentMethod +{ + const TX_VARIANT = 'googlepay'; + const NAME = 'Google Pay'; + + public function getTxVariant(): string + { + return self::TX_VARIANT; + } + + public function getPaymentMethodName(): string + { + return self::NAME; + } + + public function supportsManualCapture(): bool + { + return true; + } + + public function supportsAutoCapture(): bool + { + return true; + } + + public function supportsCardOnFile(): bool + { + return true; + } + + public function supportsSubscription(): bool + { + return true; + } + + public function supportsUnscheduledCardOnFile(): bool + { + return true; + } +} diff --git a/Helper/PaymentMethods/PayPalPaymentMethod.php b/Helper/PaymentMethods/PayPalPaymentMethod.php new file mode 100644 index 000000000..e576c2c64 --- /dev/null +++ b/Helper/PaymentMethods/PayPalPaymentMethod.php @@ -0,0 +1,53 @@ + + */ +namespace Adyen\Payment\Helper\PaymentMethods; + +class PayPalPaymentMethod implements PaymentMethodInterface +{ + const TX_VARIANT = 'paypal'; + const NAME = 'PayPal'; + + public function getTxVariant(): string + { + return self::TX_VARIANT; + } + + public function getPaymentMethodName(): string + { + return self::NAME; + } + + public function supportsManualCapture(): bool + { + return true; + } + + public function supportsAutoCapture(): bool + { + return true; + } + + public function supportsCardOnFile(): bool + { + return false; + } + + public function supportsSubscription(): bool + { + return true; + } + + public function supportsUnscheduledCardOnFile(): bool + { + return true; + } +} diff --git a/Helper/PaymentMethods/PaymentMethodFactory.php b/Helper/PaymentMethods/PaymentMethodFactory.php new file mode 100644 index 000000000..32497defb --- /dev/null +++ b/Helper/PaymentMethods/PaymentMethodFactory.php @@ -0,0 +1,51 @@ + + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +namespace Adyen\Payment\Helper\PaymentMethods; + +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Logger\AdyenLogger; + +class PaymentMethodFactory +{ + private static $adyenLogger; + + public function __construct(AdyenLogger $adyenLogger) + { + self::$adyenLogger = $adyenLogger; + } + + /** + * @throws PaymentMethodException + */ + public static function createAdyenPaymentMethod(string $txVariant): PaymentMethodInterface + { + $txVariantObject = new TxVariant($txVariant); + + switch ($txVariantObject->getPaymentMethod()) { + case PayPalPaymentMethod::TX_VARIANT: + return new PayPalPaymentMethod(); + case SepaPaymentMethod::TX_VARIANT: + return new SepaPaymentMethod(); + case ApplePayPaymentMethod::TX_VARIANT: + return new ApplePayPaymentMethod($txVariantObject->getCard()); + case AmazonPayPaymentMethod::TX_VARIANT: + return new AmazonPayPaymentMethod($txVariantObject->getCard()); + case GooglePayPaymentMethod::TX_VARIANT: + return new GooglePayPaymentMethod($txVariantObject->getCard()); + default: + $message = sprintf('Unknown txVariant: %s', $txVariant); + self::$adyenLogger->debug($message); + throw new PaymentMethodException(__($message)); + } + } +} diff --git a/Helper/PaymentMethods/PaymentMethodInterface.php b/Helper/PaymentMethods/PaymentMethodInterface.php new file mode 100644 index 000000000..1c1c53eea --- /dev/null +++ b/Helper/PaymentMethods/PaymentMethodInterface.php @@ -0,0 +1,30 @@ + + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +namespace Adyen\Payment\Helper\PaymentMethods; + +interface PaymentMethodInterface +{ + public function getTxVariant(): string; + + public function getPaymentMethodName(): string; + + public function supportsCardOnFile(): bool; + + public function supportsSubscription(): bool; + + public function supportsManualCapture(): bool; + + public function supportsAutoCapture(): bool; + + public function supportsUnscheduledCardOnFile(): bool; +} diff --git a/Helper/PaymentMethods/SepaPaymentMethod.php b/Helper/PaymentMethods/SepaPaymentMethod.php new file mode 100644 index 000000000..9587c95a2 --- /dev/null +++ b/Helper/PaymentMethods/SepaPaymentMethod.php @@ -0,0 +1,58 @@ + + */ +namespace Adyen\Payment\Helper\PaymentMethods; + +class SepaPaymentMethod implements PaymentMethodInterface +{ + const TX_VARIANT = 'sepadirectdebit'; + const NAME = 'SEPA Direct Debit'; + + public function getTxVariant(): string + { + return self::TX_VARIANT; + } + + public function getPaymentMethodName(): string + { + return self::NAME; + } + + public function supportsRecurring(): bool + { + return true; + } + + public function supportsManualCapture(): bool + { + return true; + } + + public function supportsAutoCapture(): bool + { + return true; + } + + public function supportsCardOnFile(): bool + { + return false; + } + + public function supportsSubscription(): bool + { + return true; + } + + public function supportsUnscheduledCardOnFile(): bool + { + return true; + } +} diff --git a/Helper/PaymentMethods/TxVariant.php b/Helper/PaymentMethods/TxVariant.php new file mode 100644 index 000000000..3dedb7f2f --- /dev/null +++ b/Helper/PaymentMethods/TxVariant.php @@ -0,0 +1,58 @@ + + */ + +namespace Adyen\Payment\Helper\PaymentMethods; + +class TxVariant +{ + /** @var string */ + private $card; + + /** @var string */ + private $paymentMethod; + + public function __construct(string $txVariant) + { + $splitVariant = explode('_', $txVariant, 2); + if (count($splitVariant) > 1) { + $this->card = $splitVariant[0]; + $this->paymentMethod = $splitVariant[1]; + } else { + $this->paymentMethod = $splitVariant[0]; + } + } + + public function getCard(): ?string + { + return $this->card; + } + + public function setCard(?string $card): void + { + $this->card = $card; + } + + public function getPaymentMethod(): string + { + return $this->paymentMethod; + } + + public function setPaymentMethod(string $paymentMethod): void + { + $this->paymentMethod = $paymentMethod; + } + + public function isWalletVariant(): bool + { + return isset($this->card); + } +} diff --git a/Helper/PaymentResponseHandler.php b/Helper/PaymentResponseHandler.php index 2a2933693..d735e8ffa 100644 --- a/Helper/PaymentResponseHandler.php +++ b/Helper/PaymentResponseHandler.php @@ -11,10 +11,18 @@ namespace Adyen\Payment\Helper; +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\PaymentMethods\AbstractWalletPaymentMethod; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\Ui\AdyenHppConfigProvider; +use Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider; +use Adyen\Payment\Observer\AdyenHppDataAssignObserver; use Exception; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\ResourceModel\Order; class PaymentResponseHandler { @@ -45,7 +53,7 @@ class PaymentResponseHandler private $vaultHelper; /** - * @var \Magento\Sales\Model\ResourceModel\Order + * @var Order */ private $orderResourceModel; @@ -63,6 +71,16 @@ class PaymentResponseHandler */ private $quoteHelper; + /** + * @var Config + */ + private $configHelper; + + /** + * @var PaymentMethodFactory + */ + private $paymentMethodFactory; + /** * PaymentResponseHandler constructor. * @@ -74,10 +92,12 @@ public function __construct( AdyenLogger $adyenLogger, Data $adyenHelper, Vault $vaultHelper, - \Magento\Sales\Model\ResourceModel\Order $orderResourceModel, + Order $orderResourceModel, Data $dataHelper, Recurring $recurringHelper, - Quote $quoteHelper + Quote $quoteHelper, + Config $configHelper, + PaymentMethodFactory $paymentMethodFactory ) { $this->adyenLogger = $adyenLogger; $this->adyenHelper = $adyenHelper; @@ -86,6 +106,8 @@ public function __construct( $this->dataHelper = $dataHelper; $this->recurringHelper = $recurringHelper; $this->quoteHelper = $quoteHelper; + $this->configHelper = $configHelper; + $this->paymentMethodFactory = $paymentMethodFactory; } public function formatPaymentResponse($resultCode, $action = null, $additionalData = null) @@ -129,9 +151,10 @@ public function formatPaymentResponse($resultCode, $action = null, $additionalDa /** * @param $paymentsResponse - * @param OrderPaymentInterface $payment + * @param Payment $payment * @param OrderInterface|null $order * @return bool + * @throws \Magento\Framework\Exception\LocalizedException */ public function handlePaymentResponse($paymentsResponse, $payment, $order = null) { @@ -193,14 +216,42 @@ public function handlePaymentResponse($paymentsResponse, $payment, $order = null // set transaction $payment->setTransactionId($paymentsResponse['pspReference']); } + $paymentMethodInstance = $payment->getMethodInstance(); + + if ($this->vaultHelper->hasRecurringDetailReference($paymentsResponse) && + $paymentMethodInstance->getCode() !== AdyenOneclickConfigProvider::CODE) { + $storeId = $paymentMethodInstance->getStore(); + $paymentInstanceCode = $paymentMethodInstance->getCode(); + $storePaymentMethods = $this->configHelper->isStoreAlternativePaymentMethodEnabled($storeId); - if (!empty($paymentsResponse['additionalData']['recurring.recurringDetailReference']) && - $payment->getMethodInstance()->getCode() !== \Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider::CODE) { - if ($this->vaultHelper->isCardVaultEnabled()) { - $this->vaultHelper->saveRecurringDetails($payment, $paymentsResponse['additionalData']); + if ($storePaymentMethods && $paymentInstanceCode === AdyenHppConfigProvider::CODE) { + $brand = $payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE); + try { + $adyenPaymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($brand); + if ($adyenPaymentMethod instanceof AbstractWalletPaymentMethod) { + $this->vaultHelper->saveRecurringCardDetails( + $payment, + $paymentsResponse['additionalData'] + ); + } else { + $this->vaultHelper->saveRecurringPaymentMethodDetails( + $payment, + $paymentsResponse['additionalData'] + ); + } + } catch (PaymentMethodException $e) { + $this->adyenLogger->error(sprintf( + 'Unable to create payment method with tx variant %s in details handler', + $brand + )); + } } else { $order = $payment->getOrder(); - $this->recurringHelper->createAdyenBillingAgreement($order, $paymentsResponse['additionalData']); + $this->recurringHelper->createAdyenBillingAgreement( + $order, + $paymentsResponse['additionalData'], + $payment->getAdditionalInformation() + ); } } diff --git a/Helper/Recurring.php b/Helper/Recurring.php index f99b6bde1..b3e475f29 100644 --- a/Helper/Recurring.php +++ b/Helper/Recurring.php @@ -23,6 +23,7 @@ class Recurring const CARD_ON_FILE = 'CardOnFile'; const SUBSCRIPTION = 'Subscription'; + const UNSCHEDULED_CARD_ON_FILE = 'UnscheduledCardOnFile'; /** @var AdyenLogger */ private $adyenLogger; @@ -66,7 +67,8 @@ public static function getRecurringTypes(): array { return [ self::CARD_ON_FILE, - self::SUBSCRIPTION + self::SUBSCRIPTION, + self::UNSCHEDULED_CARD_ON_FILE ]; } @@ -84,9 +86,8 @@ public static function getRecurringMethods(): array /** * @param $order * @param $additionalData - * @param array $savedPaymentData */ - public function createAdyenBillingAgreement($order, $additionalData, array $savedPaymentData = []) + public function createAdyenBillingAgreement($order, $additionalData) { if (!empty($additionalData['recurring.recurringDetailReference'])) { try { @@ -123,8 +124,6 @@ public function createAdyenBillingAgreement($order, $additionalData, array $save if ($payment->getMethod() === PaymentMethods::ADYEN_CC) { $billingAgreement->setCcBillingAgreement($additionalData, $storeOneClick, $order->getStoreId()); - } elseif ($payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE) === Data::SEPA) { - $billingAgreement->setSepaBillingAgreement($additionalData, $order->getStoreId(), $savedPaymentData); } $billingAgreementErrors = $billingAgreement->getErrors(); diff --git a/Helper/Requests.php b/Helper/Requests.php index 60571fc9c..14ffd604b 100644 --- a/Helper/Requests.php +++ b/Helper/Requests.php @@ -19,7 +19,9 @@ class Requests extends AbstractHelper { - CONST MERCHANT_ACCOUNT = 'merchantAccount'; + const MERCHANT_ACCOUNT = 'merchantAccount'; + const SHOPPER_REFERENCE = 'shopperReference'; + const RECURRING_DETAIL_REFERENCE = 'recurringDetailReference'; /** * @var Data @@ -369,7 +371,7 @@ public function buildCardRecurringData(int $storeId, $payment): array if ($storePaymentMethod) { if ($this->vaultHelper->isCardVaultEnabled()) { - $request['recurringProcessingModel'] = 'Subscription'; + $request['recurringProcessingModel'] = $this->adyenConfig->getCardRecurringType($storeId); } else { $recurringType = $this->adyenConfig->getCardRecurringType($storeId); $request['recurringProcessingModel'] = $recurringType; @@ -379,34 +381,6 @@ public function buildCardRecurringData(int $storeId, $payment): array return $request; } - /** - * Build the recurring data when payment is done trough an alternative payment method - * - * @param int $storeId - * @param $payment - * @return array - */ - public function buildAlternativePaymentRecurringData(int $storeId, $payment): array - { - $request = []; - - $brand = $payment->getAdditionalInformation(AdyenHppDataAssignObserver::BRAND_CODE); - if (!$this->adyenConfig->isStoreAlternativePaymentMethodEnabled() || - !$this->paymentMethodsHelper->paymentMethodSupportsRecurring($brand)) { - - return $request; - } - - - $recurringModel = $this->adyenConfig->getAlternativePaymentMethodTokenType($storeId); - if (isset($recurringModel)) { - $request['storePaymentMethod'] = true; - $request['recurringProcessingModel'] = $recurringModel; - } - - return $request; - } - /** * Build the recurring data to be sent in case of an Adyen Tokenized payment. * Model will be fetched according to the type (card/other pm) of the original payment diff --git a/Helper/StateData.php b/Helper/StateData.php index d10c3b4a8..0818512a6 100644 --- a/Helper/StateData.php +++ b/Helper/StateData.php @@ -87,4 +87,15 @@ public function getStateData(int $quoteId): array { return $this->stateData[$quoteId] ?? []; } + + /** + * Returns the payment method type from state data + * @param int $quoteId + * @return string + */ + public function getPaymentMethodVariant(int $quoteId): string + { + $stateDataByQuoteId = $this->stateData[$quoteId]; + return $stateDataByQuoteId['paymentMethod']['type']; + } } diff --git a/Helper/Vault.php b/Helper/Vault.php index 46f19a9e9..bd9012b28 100644 --- a/Helper/Vault.php +++ b/Helper/Vault.php @@ -12,11 +12,19 @@ namespace Adyen\Payment\Helper; +use Adyen\Payment\Exception\InvalidAdditionalDataException; +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\PaymentMethods\AbstractWalletPaymentMethod; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodInterface; use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Payment\Model\Ui\AdyenCcConfigProvider; +use Adyen\Payment\Model\Ui\AdyenHppConfigProvider; use DateInterval; use DateTime; use DateTimeZone; use Exception; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Payment\Model\InfoInterface; use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; @@ -30,6 +38,7 @@ class Vault const CARD_SUMMARY = 'cardSummary'; const EXPIRY_DATE = 'expiryDate'; const PAYMENT_METHOD = 'paymentMethod'; + const TOKEN_TYPE = 'tokenType'; const ADDITIONAL_DATA_ERRORS = [ self::RECURRING_DETAIL_REFERENCE => 'Missing Token in Result please enable in ' . 'Settings -> API URLs and Response menu in the Adyen Customer Area Recurring details setting', @@ -71,13 +80,21 @@ class Vault */ private $config; + /** @var PaymentMethods */ + private $paymentMethodsHelper; + + /** @var PaymentMethodFactory */ + private $paymentMethodFactory; + public function __construct( Data $adyenHelper, AdyenLogger $adyenLogger, PaymentTokenManagement $paymentTokenManagement, PaymentTokenFactoryInterface $paymentTokenFactory, PaymentTokenRepositoryInterface $paymentTokenRepository, - Config $config + Config $config, + PaymentMethods $paymentMethodsHelper, + PaymentMethodFactory $paymentMethodFactory ) { $this->adyenHelper = $adyenHelper; $this->adyenLogger = $adyenLogger; @@ -85,6 +102,8 @@ public function __construct( $this->paymentTokenFactory = $paymentTokenFactory; $this->paymentTokenRepository = $paymentTokenRepository; $this->config = $config; + $this->paymentMethodsHelper = $paymentMethodsHelper; + $this->paymentMethodFactory = $paymentMethodFactory; } /** @@ -100,11 +119,24 @@ public function isCardVaultEnabled($storeId = null): bool } /** - * @param $payment - * @param array $additionalData + * @param array $response + * @return bool */ - public function saveRecurringDetails($payment, array $additionalData) + public function hasRecurringDetailReference(array $response): bool { + if (array_key_exists('additionalData', $response) && + array_key_exists(self::RECURRING_DETAIL_REFERENCE, $response['additionalData'])) { + return true; + } + + return false; + } + + public function saveRecurringCardDetails( + $payment, + array $additionalData, + AbstractWalletPaymentMethod $paymentMethod = null + ) { if (!$this->isCardVaultEnabled($payment->getOrder()->getStoreId()) && !$this->adyenHelper->isHppVaultEnabled($payment->getOrder()->getStoreId())) { return; @@ -115,7 +147,7 @@ public function saveRecurringDetails($payment, array $additionalData) } try { - $paymentToken = $this->getVaultPaymentToken($payment, $additionalData); + $paymentToken = $this->getVaultPaymentToken($payment, $additionalData, $paymentMethod); } catch (Exception $exception) { $this->adyenLogger->error(json_encode($exception)); return; @@ -135,12 +167,125 @@ public function saveRecurringDetails($payment, array $additionalData) } /** + * Save recurring details related to the payment method. + * * @param $payment * @param array $additionalData * @return PaymentTokenInterface|null - * @throws Exception */ - private function getVaultPaymentToken($payment, array $additionalData): PaymentTokenInterface + public function saveRecurringPaymentMethodDetails($payment, array $additionalData): ?PaymentTokenInterface + { + try { + $paymentToken = $this->createVaultAccountToken($payment, $additionalData); + $extensionAttributes = $this->getExtensionAttributes($payment); + $extensionAttributes->setVaultPaymentToken($paymentToken); + } catch (PaymentMethodException $e) { + $this->adyenLogger->error(sprintf( + 'Unable to create token for order %s', + $payment->getOrder()->getEntityId() + )); + + return null; + } catch (Exception $exception) { + $this->adyenLogger->error($exception->getMessage()); + + return null; + } + + return $paymentToken; + } + + /** + * Build the recurring data when payment is done trough a payment method (not card) + * + * @param int $storeId + * @param $payment + * @return array + */ + public function buildPaymentMethodRecurringData(int $storeId, $brand): array + { + $request = []; + if (!$this->config->isStoreAlternativePaymentMethodEnabled()) { + return $request; + } + try { + $adyenPaymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($brand); + $allowRecurring = $this->allowRecurringOnPaymentMethod($adyenPaymentMethod, $storeId); + } catch (PaymentMethodException $exception) { + $this->adyenLogger->error(sprintf('Unable to create payment method with tx variant %s', $brand)); + return $request; + } catch (NoSuchEntityException $exception) { + $this->adyenLogger->error(sprintf('Unable to find payment method with tx variant %s', $brand)); + return $request; + } + + if (!$allowRecurring) { + return $request; + } + + $recurringModel = $this->config->getAlternativePaymentMethodTokenType($storeId); + if (isset($recurringModel)) { + $request['storePaymentMethod'] = true; + $request['recurringProcessingModel'] = $recurringModel; + } + return $request; + } + + /** + * Check if recurring should be allowed for payment method by checking: + * What type of recurring is currently enabled AND if the payment method supports that specific type recurring + * AND if the admin has enabled recurring for this payment method + * + * @param PaymentMethodInterface $adyenPaymentMethod + * @param int|null $storeId + * @return bool + * @throws NoSuchEntityException + */ + public function allowRecurringOnPaymentMethod(PaymentMethodInterface $adyenPaymentMethod, ?int $storeId): bool + { + $currentRecurringTokenSetting = $this->config->getAlternativePaymentMethodTokenType($storeId); + if ($currentRecurringTokenSetting === Recurring::CARD_ON_FILE) { + $methodSupportsRecurring = $adyenPaymentMethod->supportsCardOnFile(); + } elseif ($currentRecurringTokenSetting === Recurring::UNSCHEDULED_CARD_ON_FILE) { + $methodSupportsRecurring = $adyenPaymentMethod->supportsUnscheduledCardOnFile(); + } else { + $methodSupportsRecurring = $adyenPaymentMethod->supportsSubscription(); + } + + $tokenizedPaymentMethods = array_map( + 'trim', + explode(',', $this->config->getTokenizedPaymentMethods($storeId)) + ); + $shouldTokenize = in_array($adyenPaymentMethod->getTxVariant(), $tokenizedPaymentMethods); + + return $methodSupportsRecurring && $shouldTokenize; + } + + /** + * Return the Adyen token type (CardOnFile/Subscription) + * If it does not exist (token was created in an older version) return null + * + * @param PaymentTokenInterface $paymentToken + * @return string|null + */ + public function getAdyenTokenType(PaymentTokenInterface $paymentToken): ?string + { + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + if (array_key_exists(self::TOKEN_TYPE, $details)) { + return $details[self::TOKEN_TYPE]; + } + + return null; + } + + /** + * Create an entry in the vault table w/type=Account (for pms such as PayPal) + * If the token has already been created, do nothing + * Before doing this, validate the additionalData sent by Adyen, based on the params required by the payment method + * + * @throws InvalidAdditionalDataException + */ + private function createVaultAccountToken($payment, array $additionalData): PaymentTokenInterface { // Check if paymentToken exists already $paymentToken = $this->paymentTokenManagement->getByGatewayToken( @@ -149,6 +294,41 @@ private function getVaultPaymentToken($payment, array $additionalData): PaymentT $payment->getOrder()->getCustomerId() ); + // In case the payment token does not exist, create it based on the additionalData + if (is_null($paymentToken)) { + $storeId = $payment->getOrder()->getStoreId(); + $recurringModel = $this->config->getAlternativePaymentMethodTokenType($storeId); + $paymentToken = $this->paymentTokenFactory->create(PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT); + $paymentToken->setGatewayToken($additionalData[self::RECURRING_DETAIL_REFERENCE]); + $expiryDate = new DateTime(); + $expiryDate->add(new DateInterval('P1Y')); + $paymentToken->setExpiresAt($expiryDate); + $details = [ + 'type' => $payment->getCcType(), + self::TOKEN_TYPE => $recurringModel + ]; + + $paymentToken->setTokenDetails(json_encode($details, JSON_FORCE_OBJECT)); + } + + return $paymentToken; + } + + /** + * @throws Exception + */ + private function getVaultPaymentToken( + $payment, + array $additionalData, + AbstractWalletPaymentMethod $paymentMethod = null + ): PaymentTokenInterface { + // Check if paymentToken exists already + $paymentToken = $this->paymentTokenManagement->getByGatewayToken( + $additionalData[self::RECURRING_DETAIL_REFERENCE], + $payment->getMethodInstance()->getCode(), + $payment->getOrder()->getCustomerId() + ); + $paymentTokenSaveRequired = false; // In case the payment token does not exist, create it based on the additionalData @@ -163,7 +343,14 @@ private function getVaultPaymentToken($payment, array $additionalData): PaymentT $paymentToken->setExpiresAt($this->getExpirationDate($additionalData[self::EXPIRY_DATE])); - $details = ['type' => $additionalData[self::PAYMENT_METHOD]]; + if (isset($paymentMethod)) { + $details = [ + 'type' => $paymentMethod->getCardScheme(), + 'walletType' => $paymentMethod->getTxVariant() + ]; + } else { + $details = ['type' => $additionalData[self::PAYMENT_METHOD]]; + } if (!empty($additionalData[self::CARD_SUMMARY])) { $details['maskedCC'] = $additionalData[self::CARD_SUMMARY]; @@ -173,6 +360,18 @@ private function getVaultPaymentToken($payment, array $additionalData): PaymentT $details['expirationDate'] = $additionalData[self::EXPIRY_DATE]; } + // Set token type (alternative payment methods) for card tokens created using googlepay, applepay. + // This will be done for all card tokens once all vault changes are implemented + if ($payment->getMethodInstance()->getCode() === AdyenHppConfigProvider::CODE) { + $storeId = $payment->getOrder()->getStoreId(); + $recurringModel = $this->config->getAlternativePaymentMethodTokenType($storeId); + $details[self::TOKEN_TYPE] = $recurringModel; + } elseif ($payment->getMethodInstance()->getCode() === AdyenCcConfigProvider::CODE) { + $storeId = $payment->getOrder()->getStoreId(); + $recurringModel = $this->config->getCardRecurringType($storeId); + $details[self::TOKEN_TYPE] = $recurringModel; + } + $paymentToken->setTokenDetails(json_encode($details)); // If the token is updated, it needs to be saved to keep the changes diff --git a/Helper/Webhook/RecurringContractWebhookHandler.php b/Helper/Webhook/RecurringContractWebhookHandler.php index 1244913a5..830e6abf8 100644 --- a/Helper/Webhook/RecurringContractWebhookHandler.php +++ b/Helper/Webhook/RecurringContractWebhookHandler.php @@ -312,8 +312,6 @@ private function handlePaymentMethodContract(MagentoOrder $order, Notification $ $details = [ 'type' => $notification->getPaymentMethod(), - 'maskedCC' => $payment->getAdditionalInformation()['ibanNumber'], - 'expirationDate' => 'N/A' ]; $paymentTokenAlternativePaymentMethod->setCustomerId($customerId) diff --git a/Model/Billing/Agreement.php b/Model/Billing/Agreement.php index c695b0ed2..414492d9e 100644 --- a/Model/Billing/Agreement.php +++ b/Model/Billing/Agreement.php @@ -256,46 +256,6 @@ public function setCcBillingAgreement($contractDetail, $storeOneClick, $storeId) return $this; } - /** - * Set SEPA billing agreement This should be changed to utilise the factory method for different payment methods - * in the future - * - * @param array $additionalData - * @param $storeId - * @param array $savedPaymentData - * @return $this - */ - public function setSepaBillingAgreement(array $additionalData, $storeId, array $savedPaymentData): Agreement - { - $this - ->setMethodCode(PaymentMethods::ADYEN_ONE_CLICK) - ->setReferenceId($additionalData['recurring.recurringDetailReference']); - - $variant = $additionalData['paymentMethod']; - - $label = __( - '%1, %2', - $savedPaymentData['ownerName'], - $savedPaymentData['iban'] - ); - - $this->setAgreementLabel($label); - $recurringType = $this->configHelper->getAlternativePaymentMethodTokenType($storeId); - - $agreementData = [ - 'bank' => [ - 'ownerName' => $savedPaymentData['ownerName'], - 'iban' => $savedPaymentData['iban'], - ], - 'variant' => $variant, - 'contractTypes' => [$recurringType] - ]; - - $this->setAgreementData($agreementData); - - return $this; - } - /** * @param Payment $payment * @param $recurringDetailReference diff --git a/Model/Comment/ApiKeyEnding.php b/Model/Comment/ApiKeyEnding.php index 4476112b0..b797a95e2 100644 --- a/Model/Comment/ApiKeyEnding.php +++ b/Model/Comment/ApiKeyEnding.php @@ -1,4 +1,14 @@ + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ namespace Adyen\Payment\Model\Comment; diff --git a/Model/Comment/TokenizedPaymentMethods.php b/Model/Comment/TokenizedPaymentMethods.php new file mode 100644 index 000000000..7450b74f8 --- /dev/null +++ b/Model/Comment/TokenizedPaymentMethods.php @@ -0,0 +1,28 @@ + + * @copyright (c) 2022 Adyen B.V. + * @license https://opensource.org/licenses/MIT MIT license + * This file is open source and available under the MIT license. + * See the LICENSE file for more info. + */ + +namespace Adyen\Payment\Model\Comment; + +use Magento\Config\Model\Config\CommentInterface; + +class TokenizedPaymentMethods implements CommentInterface +{ + /** + * + * @param string $elementValue The value of the field with this commented + */ + public function getCommentText($elementValue): string + { + return 'Selected payment methods will automatically be tokenized on every transaction. + At the moment, CardOnFile tokens can only be created using Wallet payment methods (Google Pay).'; + } +} diff --git a/Model/Config/Source/TokenizedPaymentMethods.php b/Model/Config/Source/TokenizedPaymentMethods.php new file mode 100644 index 000000000..0af6ee7ca --- /dev/null +++ b/Model/Config/Source/TokenizedPaymentMethods.php @@ -0,0 +1,39 @@ + PaymentMethods\ApplePayPaymentMethod::TX_VARIANT, + 'label' => PaymentMethods\ApplePayPaymentMethod::NAME + ], + [ + 'value' => PaymentMethods\AmazonPayPaymentMethod::TX_VARIANT, + 'label' => PaymentMethods\AmazonPayPaymentMethod::NAME + ],*/ + [ + 'value' => PaymentMethods\GooglePayPaymentMethod::TX_VARIANT, + 'label' => PaymentMethods\GooglePayPaymentMethod::NAME + ], + [ + 'value' => PaymentMethods\PayPalPaymentMethod::TX_VARIANT, + 'label' => PaymentMethods\PayPalPaymentMethod::NAME + ], + [ + 'value' => PaymentMethods\SepaPaymentMethod::TX_VARIANT, + 'label' => PaymentMethods\SepaPaymentMethod::NAME + ], + ]; + } +} diff --git a/Model/InstantPurchase/AbstractAdyenTokenFormatter.php b/Model/InstantPurchase/AbstractAdyenTokenFormatter.php new file mode 100644 index 000000000..a9c4b0504 --- /dev/null +++ b/Model/InstantPurchase/AbstractAdyenTokenFormatter.php @@ -0,0 +1,94 @@ + + */ + +namespace Adyen\Payment\Model\InstantPurchase; + +use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +abstract class AbstractAdyenTokenFormatter +{ + /** + * Most used credit card types + * + * @var array + */ + public static $baseCardTypes = [ + 'AE' => 'American Express', + 'VI' => 'Visa', + 'MC' => 'MasterCard', + 'DI' => 'Discover', + 'JBC' => 'JBC', + 'CUP' => 'China Union Pay', + 'MI' => 'Maestro', + ]; + + /** + * Most used HPP types + * + * @var array + */ + public static $baseHppTypes = [ + 'sepadirectdebit' => 'SEPA Direct Debit' + ]; + + abstract public function formatPaymentToken(PaymentTokenInterface $paymentToken): string; + + public function formatCardPaymentToken(PaymentTokenInterface $paymentToken): string + { + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + + // Credit card type vaults + if (PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD === $paymentToken->getType()) { + if (!isset($details['type'], $details['maskedCC'], $details['expirationDate'])) { + throw new \InvalidArgumentException('Invalid Adyen credit card token details.'); + } + + if (isset(self::$baseCardTypes[$details['type']])) { + $ccType = self::$baseCardTypes[$details['type']]; + } else { + $ccType = $details['type']; + } + + return sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + $ccType, + __('ending'), + $details['maskedCC'], + __('expires'), + $details['expirationDate'] + ); + } else { + // Account type vaults + if (!isset($details['type'], $details['maskedCC'], $details['expirationDate'])) { + throw new \InvalidArgumentException('Invalid Adyen local payment method token details.'); + } + + if (isset(self::$baseHppTypes[$details['type']])) { + $hppType = self::$baseHppTypes[$details['type']]; + } else { + $hppType = $details['type']; + } + + return sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Account'), + $hppType, + __('number'), + $details['maskedCC'], + __('expires'), + $details['expirationDate'] + ); + } + } +} diff --git a/Model/InstantPurchase/CreditCard/TokenFormatter.php b/Model/InstantPurchase/CreditCard/TokenFormatter.php index dfd91ba6e..5f11276db 100644 --- a/Model/InstantPurchase/CreditCard/TokenFormatter.php +++ b/Model/InstantPurchase/CreditCard/TokenFormatter.php @@ -3,7 +3,7 @@ * * Adyen Payment Module * - * Copyright (c) 2019 Adyen B.V. + * Copyright (c) 2022 Adyen B.V. * This file is open source and available under the MIT license. * See the LICENSE file for more info. * @@ -12,88 +12,18 @@ namespace Adyen\Payment\Model\InstantPurchase\CreditCard; +use Adyen\Payment\Model\InstantPurchase\AbstractAdyenTokenFormatter; use Magento\InstantPurchase\PaymentMethodIntegration\PaymentTokenFormatterInterface; -use Magento\Vault\Api\Data\PaymentTokenFactoryInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; /** * Adyen stored credit card formatter. */ -class TokenFormatter implements PaymentTokenFormatterInterface +class TokenFormatter extends AbstractAdyenTokenFormatter implements PaymentTokenFormatterInterface { - /** - * Most used credit card types - * - * @var array - */ - public static $baseCardTypes = [ - 'AE' => 'American Express', - 'VI' => 'Visa', - 'MC' => 'MasterCard', - 'DI' => 'Discover', - 'JBC' => 'JBC', - 'CUP' => 'China Union Pay', - 'MI' => 'Maestro', - ]; - /** - * Most used HPP types - * - * @var array - */ - public static $baseHppTypes = [ - 'sepadirectdebit' => 'SEPA Direct Debit' - ]; - - /** - * @inheritdoc - */ public function formatPaymentToken(PaymentTokenInterface $paymentToken): string { - $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); - - // Credit card type vaults - if (PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD === $paymentToken->getType()) { - if (!isset($details['type'], $details['maskedCC'], $details['expirationDate'])) { - throw new \InvalidArgumentException('Invalid Adyen credit card token details.'); - } - - if (isset(self::$baseCardTypes[$details['type']])) { - $ccType = self::$baseCardTypes[$details['type']]; - } else { - $ccType = $details['type']; - } - - return sprintf( - '%s: %s, %s: %s (%s: %s)', - __('Credit Card'), - $ccType, - __('ending'), - $details['maskedCC'], - __('expires'), - $details['expirationDate'] - ); - } else { - // Account type vaults - if (!isset($details['type'], $details['maskedCC'], $details['expirationDate'])) { - throw new \InvalidArgumentException('Invalid Adyen local payment method token details.'); - } - - if (isset(self::$baseHppTypes[$details['type']])) { - $hppType = self::$baseHppTypes[$details['type']]; - } else { - $hppType = $details['type']; - } - - return sprintf( - '%s: %s, %s: %s (%s: %s)', - __('Account'), - $hppType, - __('number'), - $details['maskedCC'], - __('expires'), - $details['expirationDate'] - ); - } + return $this->formatCardPaymentToken($paymentToken); } } diff --git a/Model/InstantPurchase/PaymentMethods/AvailabilityChecker.php b/Model/InstantPurchase/PaymentMethods/AvailabilityChecker.php new file mode 100644 index 000000000..df9ddb663 --- /dev/null +++ b/Model/InstantPurchase/PaymentMethods/AvailabilityChecker.php @@ -0,0 +1,41 @@ + + */ + +namespace Adyen\Payment\Model\InstantPurchase\PaymentMethods; + +use Adyen\Payment\Helper\Config; +use Adyen\Payment\Helper\Recurring; +use Magento\InstantPurchase\PaymentMethodIntegration\AvailabilityCheckerInterface; + +class AvailabilityChecker implements AvailabilityCheckerInterface +{ + /** @var Config */ + private $configHelper; + + public function __construct(Config $configHelper) + { + $this->configHelper = $configHelper; + } + + /** + * Check if store alternative payment methods is set to true AND the type is set to CardOnFile + * + * @inheritdoc + */ + public function isAvailable(): bool + { + $storeVaultPmEnabled = $this->configHelper->isStoreAlternativePaymentMethodEnabled(); + $vaultPmTokenType = $this->configHelper->getAlternativePaymentMethodTokenType() === Recurring::CARD_ON_FILE; + + return $storeVaultPmEnabled && $vaultPmTokenType; + } +} diff --git a/Model/InstantPurchase/PaymentMethods/TokenFormatter.php b/Model/InstantPurchase/PaymentMethods/TokenFormatter.php new file mode 100644 index 000000000..de6dc58f3 --- /dev/null +++ b/Model/InstantPurchase/PaymentMethods/TokenFormatter.php @@ -0,0 +1,47 @@ + + */ + +namespace Adyen\Payment\Model\InstantPurchase\PaymentMethods; + +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; +use Adyen\Payment\Model\InstantPurchase\AbstractAdyenTokenFormatter; +use Magento\InstantPurchase\PaymentMethodIntegration\PaymentTokenFormatterInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +/** + * Adyen stored payment method formatter. + */ +class TokenFormatter extends AbstractAdyenTokenFormatter implements PaymentTokenFormatterInterface +{ + /** @var PaymentMethodFactory */ + private $paymentMethodFactory; + + public function __construct(PaymentMethodFactory $paymentMethodFactory) + { + $this->paymentMethodFactory = $paymentMethodFactory; + } + + public function formatPaymentToken(PaymentTokenInterface $paymentToken): string + { + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + // If payment method cannot be created based on the type, this implies that a card token was created using + // a wallet method (googlepay/applepay etc.). Hence, return the card component for this token. + try { + $adyenPaymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($details['type']); + } catch (PaymentMethodException $e) { + return $this->formatCardPaymentToken($paymentToken); + } + + return $adyenPaymentMethod->getPaymentMethodName(); + } +} diff --git a/Model/Method/PaymentMethodVault.php b/Model/Method/PaymentMethodVault.php new file mode 100644 index 000000000..f7240815c --- /dev/null +++ b/Model/Method/PaymentMethodVault.php @@ -0,0 +1,81 @@ + + */ + +namespace Adyen\Payment\Model\Method; + +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Payment\Gateway\Command\CommandManagerPoolInterface; +use Magento\Payment\Gateway\Config\ValueHandlerPoolInterface; +use Magento\Payment\Gateway\ConfigFactoryInterface; +use Magento\Payment\Gateway\ConfigInterface; +use Magento\Payment\Model\Method; +use Magento\Payment\Model\MethodInterface; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Vault\Api\PaymentTokenManagementInterface; +use Magento\Vault\Model\Method\Vault; + +class PaymentMethodVault extends Vault +{ + /** + * Constructor + * + * @param ConfigInterface $config + * @param ConfigFactoryInterface $configFactory + * @param ObjectManagerInterface $objectManager + * @param MethodInterface $vaultProvider + * @param ManagerInterface $eventManager + * @param ValueHandlerPoolInterface $valueHandlerPool + * @param CommandManagerPoolInterface $commandManagerPool + * @param PaymentTokenManagementInterface $tokenManagement + * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory + * @param string $code + * @param Json|null $jsonSerializer + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + ConfigInterface $config, + ConfigFactoryInterface $configFactory, + ObjectManagerInterface $objectManager, + MethodInterface $vaultProvider, + ManagerInterface $eventManager, + ValueHandlerPoolInterface $valueHandlerPool, + CommandManagerPoolInterface $commandManagerPool, + PaymentTokenManagementInterface $tokenManagement, + OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, + $code, + Json $jsonSerializer = null + ) { + parent::__construct( + $config, + $configFactory, + $objectManager, + $vaultProvider, + $eventManager, + $valueHandlerPool, + $commandManagerPool, + $tokenManagement, + $paymentExtensionFactory, + $code, + $jsonSerializer + ); + } + + /** + * @return false + */ + public function isInitializeNeeded(): bool + { + return false; + } +} diff --git a/Model/Ui/AdyenCcConfigProvider.php b/Model/Ui/AdyenCcConfigProvider.php index 525e93162..44809ff49 100755 --- a/Model/Ui/AdyenCcConfigProvider.php +++ b/Model/Ui/AdyenCcConfigProvider.php @@ -12,8 +12,16 @@ namespace Adyen\Payment\Model\Ui; use Adyen\Payment\Helper\Config; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\Recurring; use Magento\Checkout\Model\ConfigProviderInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Asset\Source; +use Magento\Payment\Model\CcConfig; +use Magento\Store\Model\StoreManagerInterface; class AdyenCcConfigProvider implements ConfigProviderInterface { @@ -21,12 +29,7 @@ class AdyenCcConfigProvider implements ConfigProviderInterface const CC_VAULT_CODE = 'adyen_cc_vault'; /** - * @var PaymentHelper - */ - protected $_paymentHelper; - - /** - * @var \Adyen\Payment\Helper\Data + * @var Data */ protected $_adyenHelper; @@ -38,57 +41,59 @@ class AdyenCcConfigProvider implements ConfigProviderInterface /** * Request object * - * @var \Magento\Framework\App\RequestInterface + * @var RequestInterface */ protected $_request; /** - * @var \Magento\Framework\UrlInterface + * @var UrlInterface */ protected $_urlBuilder; /** - * @var \Magento\Payment\Model\CcConfig + * @var CcConfig */ private $ccConfig; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var SerializerInterface */ private $serializer; /** @var Config $configHelper */ private $configHelper; + /** @var PaymentMethods */ + private $paymentMethodsHelper; + /** * AdyenCcConfigProvider constructor. * - * @param \Magento\Payment\Helper\Data $paymentHelper - * @param \Adyen\Payment\Helper\Data $adyenHelper - * @param \Magento\Framework\App\RequestInterface $request - * @param \Magento\Framework\UrlInterface $urlBuilder - * @param \Magento\Framework\View\Asset\Source $assetSource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Payment\Model\CcConfig $ccConfig - * @param \Magento\Framework\Serialize\SerializerInterface $serializer + * @param Data $adyenHelper + * @param RequestInterface $request + * @param UrlInterface $urlBuilder + * @param Source $assetSource + * @param StoreManagerInterface $storeManager + * @param CcConfig $ccConfig + * @param SerializerInterface $serializer + * @param Config $configHelper */ public function __construct( - \Magento\Payment\Helper\Data $paymentHelper, - \Adyen\Payment\Helper\Data $adyenHelper, - \Magento\Framework\App\RequestInterface $request, - \Magento\Framework\UrlInterface $urlBuilder, - \Magento\Framework\View\Asset\Source $assetSource, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Payment\Model\CcConfig $ccConfig, - \Magento\Framework\Serialize\SerializerInterface $serializer, - Config $configHelper + Data $adyenHelper, + RequestInterface $request, + UrlInterface $urlBuilder, + Source $assetSource, + StoreManagerInterface $storeManager, + CcConfig $ccConfig, + SerializerInterface $serializer, + Config $configHelper, + PaymentMethods $paymentMethodsHelper ) { - $this->_paymentHelper = $paymentHelper; $this->_adyenHelper = $adyenHelper; $this->_request = $request; $this->_urlBuilder = $urlBuilder; @@ -97,6 +102,7 @@ public function __construct( $this->storeManager = $storeManager; $this->serializer = $serializer; $this->configHelper = $configHelper; + $this->paymentMethodsHelper = $paymentMethodsHelper; } /** @@ -125,11 +131,15 @@ public function getConfig() [ 'payment' => [ 'ccform' => [ - 'availableTypes' => [$methodCode => $this->getCcAvailableTypes()], - 'availableTypesByAlt' => [$methodCode => $this->getCcAvailableTypesByAlt()], + 'availableTypes' => [ + $methodCode => $this->paymentMethodsHelper->getCcAvailableTypes() + ], + 'availableTypesByAlt' => [ + $methodCode => $this->paymentMethodsHelper->getCcAvailableTypesByAlt() + ], 'months' => [$methodCode => $this->getCcMonths()], 'years' => [$methodCode => $this->getCcYears()], - 'hasVerification' => [$methodCode => $this->hasVerification($methodCode)], + 'hasVerification' => [$methodCode => $this->hasVerification()], 'cvvImageUrl' => [$methodCode => $this->getCvvImageUrl()] ] ] @@ -162,50 +172,6 @@ public function getConfig() return $config; } - /** - * Retrieve available credit card types - * - * @return array - */ - protected function getCcAvailableTypes() - { - $types = []; - $ccTypes = $this->_adyenHelper->getAdyenCcTypes(); - $availableTypes = $this->_adyenHelper->getAdyenCcConfigData('cctypes'); - if ($availableTypes) { - $availableTypes = explode(',', $availableTypes); - foreach (array_keys($ccTypes) as $code) { - if (in_array($code, $availableTypes)) { - $types[$code] = $ccTypes[$code]['name']; - } - } - } - - return $types; - } - - /** - * Retrieve available credit card type codes by alt code - * - * @return array - */ - protected function getCcAvailableTypesByAlt() - { - $types = []; - $ccTypes = $this->_adyenHelper->getAdyenCcTypes(); - $availableTypes = $this->_adyenHelper->getAdyenCcConfigData('cctypes'); - if ($availableTypes) { - $availableTypes = explode(',', $availableTypes); - foreach (array_keys($ccTypes) as $code) { - if (in_array($code, $availableTypes)) { - $types[$ccTypes[$code]['code_alt']] = $code; - } - } - } - - return $types; - } - /** * Get icons for available payment methods * @@ -275,7 +241,7 @@ protected function getCvvImageUrl() /** * Retrieve request object * - * @return \Magento\Framework\App\RequestInterface + * @return RequestInterface */ protected function _getRequest() { diff --git a/Model/Ui/AdyenHppConfigProvider.php b/Model/Ui/AdyenHppConfigProvider.php index bf1736ff7..49200895c 100755 --- a/Model/Ui/AdyenHppConfigProvider.php +++ b/Model/Ui/AdyenHppConfigProvider.php @@ -18,6 +18,7 @@ class AdyenHppConfigProvider implements ConfigProviderInterface { const CODE = 'adyen_hpp'; + const HPP_VAULT_CODE = 'adyen_hpp_vault'; /** * @var PaymentHelper @@ -103,6 +104,7 @@ public function getConfig() $config = [ 'payment' => [ self::CODE => [ + 'vaultCode' => self::HPP_VAULT_CODE, 'isActive' => true, 'successPage' => $this->urlBuilder->getUrl( 'checkout/onepage/success', diff --git a/Model/Ui/AdyenOneclickConfigProvider.php b/Model/Ui/AdyenOneclickConfigProvider.php index 78d3afe4d..9e4c997b2 100644 --- a/Model/Ui/AdyenOneclickConfigProvider.php +++ b/Model/Ui/AdyenOneclickConfigProvider.php @@ -13,6 +13,7 @@ use Adyen\Payment\Helper\ChargedCurrency; use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentMethods; use Adyen\Payment\Helper\Recurring; use Adyen\Payment\Helper\Vault; use Magento\Checkout\Model\ConfigProviderInterface; @@ -77,6 +78,9 @@ class AdyenOneclickConfigProvider implements ConfigProviderInterface */ private $vaultHelper; + /** @var PaymentMethods */ + private $paymentMethodsHelper; + /** * AdyenOneclickConfigProvider constructor. * @@ -99,7 +103,8 @@ public function __construct( UrlInterface $urlBuilder, CcConfig $ccConfig, ChargedCurrency $chargedCurrency, - Vault $vaultHelper + Vault $vaultHelper, + PaymentMethods $paymentMethodsHelper ) { $this->_adyenHelper = $adyenHelper; $this->_request = $request; @@ -110,6 +115,7 @@ public function __construct( $this->ccConfig = $ccConfig; $this->chargedCurrency = $chargedCurrency; $this->vaultHelper = $vaultHelper; + $this->paymentMethodsHelper = $paymentMethodsHelper; } /** @@ -146,7 +152,7 @@ public function getConfig() [ 'payment' => [ 'ccform' => [ - 'availableTypes' => [$methodCode => $this->getCcAvailableTypes()], + 'availableTypes' => [$methodCode => $this->paymentMethodsHelper->getCcAvailableTypes()], 'months' => [$methodCode => $this->getCcMonths()], 'years' => [$methodCode => $this->getCcYears()], 'hasVerification' => [$methodCode => $this->hasVerification($methodCode)], @@ -203,28 +209,6 @@ protected function _getQuote() return $this->_session->getQuote(); } - /** - * Retrieve availables credit card types - * - * @return array - */ - protected function getCcAvailableTypes() - { - $types = []; - $ccTypes = $this->_adyenHelper->getAdyenCcTypes(); - $availableTypes = $this->_adyenHelper->getAdyenCcConfigData('cctypes'); - if ($availableTypes) { - $availableTypes = explode(',', $availableTypes); - foreach (array_keys($ccTypes) as $code) { - if (in_array($code, $availableTypes)) { - $types[$code] = $ccTypes[$code]['name']; - } - } - } - - return $types; - } - /** * Retrieve credit card expire months * diff --git a/Model/Ui/AdyenUiComponentProvider.php b/Model/Ui/AdyenUiComponentProvider.php new file mode 100644 index 000000000..cd7d9af34 --- /dev/null +++ b/Model/Ui/AdyenUiComponentProvider.php @@ -0,0 +1,60 @@ + + */ + +namespace Adyen\Payment\Model\Ui; + +use Adyen\Payment\Helper\Data; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterface; +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; + +class AdyenUiComponentProvider +{ + + protected $componentFactory; + protected $dataHelper; + + /** + * @param TokenUiComponentInterfaceFactory $componentFactory + * @param Data $dataHelper + */ + public function __construct( + TokenUiComponentInterfaceFactory $componentFactory, + Data $dataHelper + ) { + $this->componentFactory = $componentFactory; + $this->dataHelper = $dataHelper; + } + + /** + * Get UI component for token + * + * @param PaymentTokenInterface $paymentToken + * @return TokenUiComponentInterface + */ + public function getCardComponentForToken(PaymentTokenInterface $paymentToken): TokenUiComponentInterface + { + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + $details['icon'] = $this->dataHelper->getVariantIcon($details['type']); + + return $this->componentFactory->create( + [ + 'config' => [ + 'code' => AdyenCcConfigProvider::CC_VAULT_CODE, + TokenUiComponentProviderInterface::COMPONENT_DETAILS => $details, + TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash() + ], + 'name' => 'Adyen_Payment/js/view/payment/method-renderer/vault' + ] + ); + } +} diff --git a/Model/Ui/PaymentMethodUiComponentProvider.php b/Model/Ui/PaymentMethodUiComponentProvider.php new file mode 100644 index 000000000..0d4fdab10 --- /dev/null +++ b/Model/Ui/PaymentMethodUiComponentProvider.php @@ -0,0 +1,85 @@ + + */ + +namespace Adyen\Payment\Model\Ui; + +use Adyen\Payment\Exception\PaymentMethodException; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\PaymentMethods\PaymentMethodFactory; +use Adyen\Payment\Helper\Recurring; +use Adyen\Payment\Helper\Vault; +use Exception; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterface; +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; + +class PaymentMethodUiComponentProvider extends AdyenUiComponentProvider implements TokenUiComponentProviderInterface +{ + + private $vaultHelper; + private $paymentMethodFactory; + + /** + * @param TokenUiComponentInterfaceFactory $componentFactory + * @param Data $dataHelper + * @param Vault $vaultHelper + * @param PaymentMethodFactory $paymentMethodFactory + */ + public function __construct( + TokenUiComponentInterfaceFactory $componentFactory, + Data $dataHelper, + Vault $vaultHelper, + PaymentMethodFactory $paymentMethodFactory + ) { + parent::__construct($componentFactory, $dataHelper); + $this->vaultHelper = $vaultHelper; + $this->paymentMethodFactory = $paymentMethodFactory; + } + + /** + * Get UI component for token + * + * @param PaymentTokenInterface $paymentToken + * @return TokenUiComponentInterface + * @throws PaymentMethodException + * @throws Exception + */ + public function getComponentForToken(PaymentTokenInterface $paymentToken): TokenUiComponentInterface + { + $tokenType = $this->vaultHelper->getAdyenTokenType($paymentToken); + $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + // If payment method cannot be created based on the type, this implies that a card token was created using + // a wallet method (googlepay/applepay etc.). Hence, return the card component for this token. + try { + $adyenPaymentMethod = $this->paymentMethodFactory::createAdyenPaymentMethod($details['type']); + } catch (PaymentMethodException $exception) { + return $this->getCardComponentForToken($paymentToken); + } + + $details['icon'] = $this->dataHelper->getVariantIcon($details['type']); + $createdAt = new \DateTime($paymentToken->getCreatedAt()); + $details['created'] = $createdAt->format('Y-m-d'); + $details['displayToken'] = $tokenType === Recurring::CARD_ON_FILE; + $details['label'] = $adyenPaymentMethod->getPaymentMethodName(); + + return $this->componentFactory->create( + [ + 'config' => [ + 'code' => AdyenHppConfigProvider::HPP_VAULT_CODE, + TokenUiComponentProviderInterface::COMPONENT_DETAILS => $details, + TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash() + ], + 'name' => 'Adyen_Payment/js/view/payment/method-renderer/payment_method_vault' + ] + ); + } +} diff --git a/Model/Ui/TokenUiComponentProvider.php b/Model/Ui/TokenUiComponentProvider.php index 5b9c9a7fc..da1884217 100644 --- a/Model/Ui/TokenUiComponentProvider.php +++ b/Model/Ui/TokenUiComponentProvider.php @@ -12,57 +12,23 @@ namespace Adyen\Payment\Model\Ui; use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\Vault; +use Adyen\Payment\Helper\Recurring; use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Model\Ui\TokenUiComponentInterface; use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; -use Magento\Framework\UrlInterface; -class TokenUiComponentProvider implements TokenUiComponentProviderInterface +class TokenUiComponentProvider extends AdyenUiComponentProvider implements TokenUiComponentProviderInterface { - /** - * @var TokenUiComponentInterfaceFactory - */ - private $componentFactory; - - /** - * @var Data - */ - private $adyenHelper; - - /** - * @param TokenUiComponentInterfaceFactory $componentFactory - * @param UrlInterface $urlBuilder - */ - public function __construct( - TokenUiComponentInterfaceFactory $componentFactory, - Data $adyenHelper - ) { - $this->componentFactory = $componentFactory; - $this->adyenHelper = $adyenHelper; - } - /** * Get UI component for token * * @param PaymentTokenInterface $paymentToken * @return TokenUiComponentInterface */ - public function getComponentForToken(PaymentTokenInterface $paymentToken) + public function getComponentForToken(PaymentTokenInterface $paymentToken): TokenUiComponentInterface { - $details = json_decode($paymentToken->getTokenDetails() ?: '{}', true); - $details['icon'] = $this->adyenHelper->getVariantIcon($details['type']); - - $component = $this->componentFactory->create( - [ - 'config' => [ - 'code' => AdyenCcConfigProvider::CC_VAULT_CODE, - TokenUiComponentProviderInterface::COMPONENT_DETAILS => $details, - TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash() - ], - 'name' => 'Adyen_Payment/js/view/payment/method-renderer/vault' - ] - ); - return $component; + return $this->getCardComponentForToken($paymentToken); } } diff --git a/Observer/AdyenHppDataAssignObserver.php b/Observer/AdyenHppDataAssignObserver.php index 316f1a77f..332b7c44e 100644 --- a/Observer/AdyenHppDataAssignObserver.php +++ b/Observer/AdyenHppDataAssignObserver.php @@ -11,7 +11,6 @@ namespace Adyen\Payment\Observer; -use Adyen\Payment\Helper\Data; use Adyen\Payment\Helper\StateData; use Adyen\Payment\Model\ResourceModel\StateData\Collection; use Adyen\Service\Validator\CheckoutStateDataValidator; @@ -19,6 +18,7 @@ use Magento\Framework\Event\Observer; use Magento\Payment\Observer\AbstractDataAssignObserver; use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Store\Model\StoreManagerInterface; class AdyenHppDataAssignObserver extends AbstractDataAssignObserver { @@ -55,21 +55,27 @@ class AdyenHppDataAssignObserver extends AbstractDataAssignObserver */ private $stateData; + /** @var StoreManagerInterface */ + private $storeManager; + /** * AdyenHppDataAssignObserver constructor. * * @param CheckoutStateDataValidator $checkoutStateDataValidator * @param Collection $stateDataCollection * @param StateData $stateData + * @param StoreManagerInterface $storeManager */ public function __construct( CheckoutStateDataValidator $checkoutStateDataValidator, Collection $stateDataCollection, - StateData $stateData + StateData $stateData, + StoreManagerInterface $storeManager ) { $this->checkoutStateDataValidator = $checkoutStateDataValidator; $this->stateDataCollection = $stateDataCollection; $this->stateData = $stateData; + $this->storeManager = $storeManager; } /** @@ -108,10 +114,6 @@ public function execute(Observer $observer) $this->stateData->setStateData($stateData, $paymentInfo->getData('quote_id')); } - if (array_key_exists(self::BRAND_CODE, $additionalData) && $additionalData[self::BRAND_CODE] === Data::SEPA) { - $additionalDataToSave = $this->getSepaAdditionalDataToSave($stateData); - } - unset($additionalData[self::STATE_DATA]); @@ -120,29 +122,10 @@ public function execute(Observer $observer) $paymentInfo->setAdditionalInformation($key, $data); } - // set ccType + // Set ccType. If payment method is tokenizable, update additional information if (!empty($additionalData[self::BRAND_CODE])) { - $paymentInfo->setCcType($additionalData[self::BRAND_CODE]); + $paymentMethod = $additionalData[self::BRAND_CODE]; + $paymentInfo->setCcType($paymentMethod); } } - - /** - * Get the additional data to save. This data will be required if the payment is to be tokenized - * - * @param array $stateData - * @return array - */ - private function getSepaAdditionalDataToSave(array $stateData): array - { - $additionalData = []; - if (array_key_exists('iban', $stateData['paymentMethod'])) { - $additionalData['iban'] = $stateData['paymentMethod']['iban']; - } - - if (array_key_exists('ownerName', $stateData['paymentMethod'])) { - $additionalData['ownerName'] = $stateData['paymentMethod']['ownerName']; - } - - return $additionalData; - } } diff --git a/Observer/VaultDeleteTokenObserver.php b/Observer/VaultDeleteTokenObserver.php new file mode 100644 index 000000000..f4a007e95 --- /dev/null +++ b/Observer/VaultDeleteTokenObserver.php @@ -0,0 +1,108 @@ + + */ + +namespace Adyen\Payment\Observer; + +use Adyen\AdyenException; +use Adyen\Payment\Helper\Data; +use Adyen\Payment\Helper\Requests; +use Adyen\Payment\Logger\AdyenLogger; +use Adyen\Service\Recurring; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Api\PaymentTokenManagementInterface; + +class VaultDeleteTokenObserver implements ObserverInterface +{ + /** @var PaymentTokenManagementInterface */ + private $paymentTokenManagement; + + /** @var Session */ + private $customerSession; + + /** @var Data */ + private $dataHelper; + + /** @var Requests */ + private $requestsHelper; + + /** @var AdyenLogger */ + private $adyenLogger; + + public function __construct( + PaymentTokenManagementInterface $paymentTokenManagement, + Session $customerSession, + Data $dataHelper, + Requests $requestsHelper, + AdyenLogger $adyenLogger + ) { + $this->paymentTokenManagement = $paymentTokenManagement; + $this->customerSession = $customerSession; + $this->dataHelper = $dataHelper; + $this->requestsHelper = $requestsHelper; + $this->adyenLogger = $adyenLogger; + } + + public function execute(Observer $observer) + { + $customerId = $this->customerSession->getCustomerId(); + $paymentToken = $this->getPaymentToken($observer->getData('request'), $customerId); + + $request = [ + Requests::MERCHANT_ACCOUNT => $this->dataHelper->getAdyenMerchantAccount( + $paymentToken->getPaymentMethodCode() + ), + Requests::SHOPPER_REFERENCE => $this->requestsHelper->getShopperReference($customerId, null), + Requests::RECURRING_DETAIL_REFERENCE => $paymentToken->getGatewayToken() + ]; + + try { + $client = $this->dataHelper->initializeAdyenClient(); + $recurringService = new Recurring($client); + $recurringService->disable($request); + } catch (AdyenException $e) { + $this->adyenLogger->error(sprintf( + 'Error while attempting to disable token with id %s: %s', + $paymentToken->getEntityId(), + $e->getMessage()) + ); + } catch (NoSuchEntityException $e) { + $this->adyenLogger->error(sprintf( + 'No such entity while attempting to disable token with id %s: %s', + $paymentToken->getEntityId(), + $e->getMessage()) + ); + } + } + + /** + * @param Http $request + * @param string $customerId + * @return PaymentTokenInterface|null + */ + private function getPaymentToken(Http $request, string $customerId): ?PaymentTokenInterface + { + $publicHash = $request->getPostValue(PaymentTokenInterface::PUBLIC_HASH); + + if ($publicHash === null) { + return null; + } + + return $this->paymentTokenManagement->getByPublicHash( + $publicHash, + $customerId + ); + } +} diff --git a/Plugin/CustomerFilterVaultTokens.php b/Plugin/CustomerFilterVaultTokens.php new file mode 100644 index 000000000..20d2001a3 --- /dev/null +++ b/Plugin/CustomerFilterVaultTokens.php @@ -0,0 +1,48 @@ + + */ + +namespace Adyen\Payment\Plugin; + +use Adyen\Payment\Helper\Vault; +use Magento\Vault\Model\CustomerTokenManagement; +use Adyen\Payment\Helper\Recurring; + +class CustomerFilterVaultTokens +{ + /** + * Returns filtered list of payment tokens for current customer session + * Hide token if it is specifically set to SUBSCRIPTION or UNSCHEDULED_CARD_ON_FILE + * + * @param CustomerTokenManagement $customerTokenManagement + * @param array $customerSessionTokens + * @return array + */ + public function afterGetCustomerSessionTokens( + CustomerTokenManagement $customerTokenManagement, + array $customerSessionTokens + ): array { + foreach ($customerSessionTokens as $key => $token) { + if (strpos($token->getPaymentMethodCode(), 'adyen_') === 0) { + $tokenDetails = json_decode($token->getTokenDetails()); + if (property_exists($tokenDetails, Vault::TOKEN_TYPE) && + in_array($tokenDetails->tokenType, [ + Recurring::SUBSCRIPTION, + Recurring::UNSCHEDULED_CARD_ON_FILE] + ) + ) { + unset($customerSessionTokens[$key]); + } + } + } + + return $customerSessionTokens; + } +} diff --git a/Plugin/PaymentVaultDeleteToken.php b/Plugin/PaymentVaultDeleteToken.php index 47a5923b8..57dd51939 100644 --- a/Plugin/PaymentVaultDeleteToken.php +++ b/Plugin/PaymentVaultDeleteToken.php @@ -3,7 +3,7 @@ * * Adyen Payment module (https://www.adyen.com/) * - * Copyright (c) 2019 Adyen BV (https://www.adyen.com/) + * Copyright (c) 2022 Adyen BV (https://www.adyen.com/) * See LICENSE.txt for license details. * * Author: Adyen @@ -11,50 +11,62 @@ namespace Adyen\Payment\Plugin; +use Adyen\Payment\Model\Api\PaymentRequest; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Api\PaymentTokenRepositoryInterface; class PaymentVaultDeleteToken { /** - * @var \Adyen\Payment\Model\Api\PaymentRequest + * @var PaymentRequest */ protected $paymentRequest; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** * PaymentVaultDeleteToken constructor. * - * @param \Adyen\Payment\Model\Api\PaymentRequest $paymentRequest + * @param PaymentRequest $paymentRequest + * @param StoreManagerInterface $storeManager */ public function __construct( - \Adyen\Payment\Model\Api\PaymentRequest $paymentRequest, - \Magento\Store\Model\StoreManagerInterface $storeManager + PaymentRequest $paymentRequest, + StoreManagerInterface $storeManager ) { $this->paymentRequest = $paymentRequest; $this->storeManager = $storeManager; } + /** + * @throws NoSuchEntityException + * @throws LocalizedException + */ public function beforeDelete( - \Magento\Vault\Api\PaymentTokenRepositoryInterface $subject, + PaymentTokenRepositoryInterface $subject, PaymentTokenInterface $paymentToken ) { $paymentMethodCode = $paymentToken->getPaymentMethodCode(); + $storeId = $this->storeManager->getStore()->getStoreId(); if (is_null($paymentMethodCode) || strpos($paymentMethodCode, 'adyen_') !== 0) { return [$paymentToken]; } + try { $this->paymentRequest->disableRecurringContract( $paymentToken->getGatewayToken(), $paymentToken->getCustomerId(), - $this->storeManager->getStore()->getStoreId() + $storeId ); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__('Failed to disable this contract')); + throw new LocalizedException(__('Failed to disable this contract')); } } } diff --git a/Test/Unit/Helper/RequestsTest.php b/Test/Unit/Helper/RequestsTest.php index c443febd9..4d69eee61 100644 --- a/Test/Unit/Helper/RequestsTest.php +++ b/Test/Unit/Helper/RequestsTest.php @@ -38,7 +38,7 @@ public function testBuildCardRecurringStorePaymentMethodFalse() public function testBuildCardRecurringStorePaymentMethodTrueVault() { - $this->setMockObjects(['storePaymentMethod' => true], true, ''); + $this->setMockObjects(['storePaymentMethod' => true], true, Recurring::SUBSCRIPTION); $request = $this->sut->buildCardRecurringData(1, $this->paymentMock); $this->assertTrue($request['storePaymentMethod']); @@ -63,7 +63,7 @@ public function testBuildCardRecurringStorePaymentMethodTrueAdyenSubscription() $this->assertEquals(Recurring::SUBSCRIPTION, $request['recurringProcessingModel']); } - private function setMockObjects(array $stateDataArray, bool $vaultEnabled, string $adyenTokenType): void + private function setMockObjects(array $stateDataArray, bool $vaultEnabled, string $tokenType): void { $stateDataMock = $this->createConfiguredMock(StateData::class, [ 'getStateData' => $stateDataArray @@ -75,7 +75,7 @@ private function setMockObjects(array $stateDataArray, bool $vaultEnabled, strin $configHelperMock = $this->createConfiguredMock(Config::class, [ - 'getCardRecurringType' => $adyenTokenType + 'getCardRecurringType' => $tokenType ]); $this->sut = new Requests( diff --git a/etc/adminhtml/system/adyen_hpp.xml b/etc/adminhtml/system/adyen_hpp.xml index 21767b61f..ad0c1b711 100755 --- a/etc/adminhtml/system/adyen_hpp.xml +++ b/etc/adminhtml/system/adyen_hpp.xml @@ -33,13 +33,24 @@ Magento\Config\Model\Config\Source\Yesno - Alternative payment methods will only be shown during checkout if Magento Vault is not being used. Currently only SEPA tokenization is supported. + Alternative payment methods will only be shown during checkout if Magento Vault is enabled. If this is option enabled, all Apple pay, Google pay, SEPA and PayPal payment methods will be tokenized. payment/adyen_hpp_vault/active 1 - + + + payment/adyen_hpp/tokenized_payment_methods + Adyen\Payment\Model\Config\Source\TokenizedPaymentMethods + Selected payment methods will be tokenized. Please make sure to enable these payment methods in your Adyen Customer Area. + + + 1 + 1 + + + Adyen\Payment\Model\Config\Source\Recurring\RecurringType payment/adyen_hpp/token_type diff --git a/etc/adminhtml/system/adyen_oneclick.xml b/etc/adminhtml/system/adyen_oneclick.xml index 0bb7a080e..4e203897e 100755 --- a/etc/adminhtml/system/adyen_oneclick.xml +++ b/etc/adminhtml/system/adyen_oneclick.xml @@ -26,9 +26,9 @@ - - Choose between using Magento Vault (one-click CVC-less payments) or the more secure Adyen Tokenization (one-click CVC required payments) functionality - + + Magento Vault (one-click CVC-less payments) or secure Adyen Tokenization (one-click CVC required payments) + Adyen\Payment\Model\Config\Source\Recurring\RecurringMode payment/adyen_oneclick/card_mode @@ -38,16 +38,12 @@ - Choose between CardOnFile (one-click payments) or Subscription (fixed schedule payments) + Choose between CardOnFile (one-click), UnscheduledCardOnFile (non-fixed schedule MIT payments) or Subscription (fixed schedule MIT payments) Adyen\Payment\Model\Config\Source\Recurring\RecurringType payment/adyen_oneclick/card_type More information]]> - - 1 - Adyen Tokenization - diff --git a/etc/config.xml b/etc/config.xml index 095e6bbad..5e44e6c1f 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -104,8 +104,18 @@ 1 1 1 + 1 + 1 adyen + + AdyenPaymentHppVaultFacade + Stored Payment Methods (Adyen) + + Adyen\Payment\Model\InstantPurchase\PaymentMethods\AvailabilityChecker + Adyen\Payment\Model\InstantPurchase\PaymentMethods\TokenFormatter + + 0 AdyenPaymentPosCloudFacade diff --git a/etc/di.xml b/etc/di.xml index 3af5ff21a..3dc57b297 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -52,6 +52,15 @@ + + + Adyen\Payment\Model\Ui\AdyenHppConfigProvider::HPP_VAULT_CODE + AdyenPaymentHppVaultConfig + AdyenPaymentHppVaultPaymentValueHandlerPool + AdyenPaymentHppFacade + + + Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider::CODE @@ -116,6 +125,18 @@ + + + AdyenPaymentHppVaultConfig + + + + + + AdyenPaymentHppVaultPaymentValueHandler + + + @@ -242,6 +263,12 @@ + + + Adyen\Payment\Model\Ui\AdyenHppConfigProvider::HPP_VAULT_CODE + + + Adyen\Payment\Model\Ui\AdyenOneclickConfigProvider::CODE @@ -301,10 +328,16 @@ AdyenPaymentCcCommandPool + + + AdyenPaymentHppCommandPool + + AdyenPaymentCcCommandManager + AdyenPaymentHppCommandManager @@ -325,10 +358,10 @@ AdyenPaymentCcVaultAuthorizeRequest Adyen\Payment\Gateway\Http\TransferFactory - Adyen\Payment\Gateway\Http\Client\TransactionAuthorization + Adyen\Payment\Gateway\Http\Client\TransactionPayment CheckoutResponseValidator - AdyenPaymentCcVaultResponseHandlerComposite + AdyenPaymentVaultResponseHandlerComposite @@ -348,10 +381,22 @@ AdyenPaymentCancelCommand AdyenPaymentRefundCommand AdyenPaymentCancelCommand + AdyenPaymentHppVaultAuthorizeCommand + AdyenPaymentHppVaultAuthorizeCommand + + + AdyenPaymentHppVaultAuthorizeRequest + Adyen\Payment\Gateway\Http\TransferFactory + Adyen\Payment\Gateway\Http\Client\TransactionPayment + CheckoutResponseValidator + AdyenPaymentVaultResponseHandlerComposite + + + @@ -605,7 +650,29 @@ - + + + + Adyen\Payment\Gateway\Request\MerchantAccountDataBuilder + + Adyen\Payment\Gateway\Request\CustomerDataBuilder + Adyen\Payment\Gateway\Request\CustomerIpDataBuilder + Adyen\Payment\Gateway\Request\AddressDataBuilder + Adyen\Payment\Gateway\Request\PaymentDataBuilder + Adyen\Payment\Gateway\Request\RiskDataBuilder + Adyen\Payment\Gateway\Request\AdditionalDataLevel23DataBuilder + Adyen\Payment\Gateway\Request\ReturnUrlDataBuilder + Adyen\Payment\Gateway\Request\ChannelDataBuilder + Adyen\Payment\Gateway\Request\OriginDataBuilder + Adyen\Payment\Gateway\Request\BrowserInfoDataBuilder + Adyen\Payment\Gateway\Request\RecurringVaultDataBuilder + Adyen\Payment\Gateway\Request\ShopperInteractionDataBuilder + + + + + @@ -1092,6 +1159,9 @@ + + + diff --git a/etc/events.xml b/etc/events.xml index 9251cc98d..fb86338cd 100644 --- a/etc/events.xml +++ b/etc/events.xml @@ -44,4 +44,7 @@ + + + diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 519923edf..cd105d18c 100755 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -28,6 +28,7 @@ Adyen\Payment\Model\Ui\TokenUiComponentProvider + Adyen\Payment\Model\Ui\PaymentMethodUiComponentProvider diff --git a/view/frontend/layout/vault_cards_listaction.xml b/view/frontend/layout/vault_cards_listaction.xml index ff85c8a35..d31d58319 100644 --- a/view/frontend/layout/vault_cards_listaction.xml +++ b/view/frontend/layout/vault_cards_listaction.xml @@ -15,8 +15,10 @@ - + + + + diff --git a/view/frontend/templates/customer_account/payment_method.phtml b/view/frontend/templates/customer_account/payment_method.phtml new file mode 100644 index 000000000..c83c17d44 --- /dev/null +++ b/view/frontend/templates/customer_account/payment_method.phtml @@ -0,0 +1,37 @@ + + + + + + +
+ getBlockHtml('formkey') ?> + + +
+ + diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-vault-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-vault-method.js new file mode 100644 index 000000000..7353aff60 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-vault-method.js @@ -0,0 +1,66 @@ +/** + * + * Adyen Payment module (https://www.adyen.com/) + * + * Copyright (c) 2022 Adyen NV (https://www.adyen.com/) + * See LICENSE.txt for license details. + * + * Author: Adyen + */ +/*browser:true*/ +/*global define*/ +define([ + 'jquery', + 'Magento_Vault/js/view/payment/method-renderer/vault' +], function ($, VaultComponent) { + 'use strict'; + + return VaultComponent.extend({ + defaults: { + template: 'Adyen_Payment/payment/payment-method-vault' + }, + /** + * Check if token should be displayed + * @returns {boolean} + */ + displayToken: function() { + return this.details.displayToken; + }, + /** + * Get tx_variant + * @returns {String} + */ + getPaymentMethodType: function () { + return this.details.type; + }, + + /** + * Get payment method name + * @returns {String} + */ + getLabel: function () { + return this.details.label; + }, + + /** + * Get expiration date + * @returns {String} + */ + getCreatedDate: function () { + return this.details.created; + }, + /** + * @returns {String} + */ + getToken: function () { + return this.publicHash; + }, + /** + * @param {String} type + * @returns {Boolean} + */ + getIcons: function (type) { + return this.details.icon; + } + }); +}); diff --git a/view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js b/view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js new file mode 100644 index 000000000..e784fb427 --- /dev/null +++ b/view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js @@ -0,0 +1,66 @@ +/** + * + * Adyen Payment module (https://www.adyen.com/) + * + * Copyright (c) 2019 Adyen BV (https://www.adyen.com/) + * See LICENSE.txt for license details. + * + * Author: Adyen + */ +/*browser:true*/ +/*global define*/ +define([ + 'jquery', + 'Magento_Vault/js/view/payment/method-renderer/vault' +], function ($, VaultComponent) { + 'use strict'; + + return VaultComponent.extend({ + defaults: { + template: 'Adyen_Payment/payment/payment-method-vault-form' + }, + /** + * Check if token should be displayed + * @returns {boolean} + */ + displayToken: function() { + return this.details.displayToken; + }, + /** + * Get tx_variant + * @returns {String} + */ + getPaymentMethodType: function () { + return this.details.type; + }, + + /** + * Get payment method name + * @returns {String} + */ + getLabel: function () { + return this.details.label; + }, + + /** + * Get expiration date + * @returns {String} + */ + getCreatedDate: function () { + return this.details.created; + }, + /** + * @returns {String} + */ + getToken: function () { + return this.publicHash; + }, + /** + * @param {String} type + * @returns {Boolean} + */ + getIcons: function (type) { + return this.details.icon; + } + }); +}); diff --git a/view/frontend/web/template/payment/payment-method-vault-form.html b/view/frontend/web/template/payment/payment-method-vault-form.html new file mode 100644 index 000000000..f14f9f03d --- /dev/null +++ b/view/frontend/web/template/payment/payment-method-vault-form.html @@ -0,0 +1,45 @@ + +
+
+ + +
+ +
+
+
+ +
+
+
+
diff --git a/view/frontend/web/template/payment/payment-method-vault.html b/view/frontend/web/template/payment/payment-method-vault.html new file mode 100644 index 000000000..6dda8fa5e --- /dev/null +++ b/view/frontend/web/template/payment/payment-method-vault.html @@ -0,0 +1,39 @@ +
+
+ + +
+ +
+
+
+ +
+
+
+