diff --git a/Model/Ui/CardTokenUiComponentProvider.php b/Model/Ui/CardTokenUiComponentProvider.php index 11dc387a1..943356633 100644 --- a/Model/Ui/CardTokenUiComponentProvider.php +++ b/Model/Ui/CardTokenUiComponentProvider.php @@ -59,7 +59,7 @@ public function getComponentForToken(PaymentTokenInterface $paymentToken): Token ['_secure' => $this->request->isSecure()] ) ], - 'name' => 'Adyen_Payment/js/view/payment/method-renderer/vault' + 'name' => 'Adyen_Payment/js/view/payment/method-renderer/adyen-cc-vault-method' ] ); } diff --git a/Model/Ui/PaymentMethodTokenUiComponentProvider.php b/Model/Ui/PaymentMethodTokenUiComponentProvider.php index 49f1c2ead..c7fab896e 100644 --- a/Model/Ui/PaymentMethodTokenUiComponentProvider.php +++ b/Model/Ui/PaymentMethodTokenUiComponentProvider.php @@ -51,7 +51,7 @@ public function getComponentForToken(PaymentTokenInterface $paymentToken): Token TokenUiComponentProviderInterface::COMPONENT_DETAILS => $details, TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash() ], - 'name' => 'Adyen_Payment/js/view/payment/method-renderer/payment_method_vault' + 'name' => 'Adyen_Payment/js/view/payment/method-renderer/adyen-pm-vault-method' ] ); } diff --git a/view/frontend/templates/checkout/multishipping/billing.phtml b/view/frontend/templates/checkout/multishipping/billing.phtml index e142f5630..e58aaa071 100644 --- a/view/frontend/templates/checkout/multishipping/billing.phtml +++ b/view/frontend/templates/checkout/multishipping/billing.phtml @@ -114,8 +114,8 @@ id="p_method_escapeHtml($code); ?>" value="escapeHtml($code); ?>" name="payment[method]" - data-bind=" - value: getCode()" + title="escapeHtml($_method->getTitle()) ?>" + data-bind="value: getCode(), afterRender: selectPaymentMethod" checked="checked" class="radio solo method" /> diff --git a/view/frontend/web/js/model/payment-component-states.js b/view/frontend/web/js/model/payment-component-states.js new file mode 100644 index 000000000..add8761a9 --- /dev/null +++ b/view/frontend/web/js/model/payment-component-states.js @@ -0,0 +1,49 @@ +/** + * + * Adyen Payment module (https://www.adyen.com/) + * + * Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/) + * See LICENSE.txt for license details. + * + * Author: Adyen + */ + +define([ + 'uiComponent', + 'ko', +], function ( + Component, + ko +) { + 'use strict'; + return Component.extend({ + states: [], + + initializeState: function ( + methodCode, + isPlaceOrderAllowed = false + ) { + this.states[methodCode] = { + isPlaceOrderAllowed: ko.observable(isPlaceOrderAllowed) + }; + }, + + getState: function (methodCode) { + if (!!this.states[methodCode]) { + return this.states[methodCode]; + } else { + throw "Payment component state does not exist!"; + } + }, + + setIsPlaceOrderAllowed: function (methodCode, isPlaceOrderAllowed) { + this.getState(methodCode); + this.states[methodCode].isPlaceOrderAllowed(isPlaceOrderAllowed); + }, + + getIsPlaceOrderAllowed: function (methodCode) { + let state = this.getState(methodCode); + return state.isPlaceOrderAllowed(); + } + }); +}); \ No newline at end of file diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js index c0e806310..6986f4f42 100755 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-cc-method.js @@ -50,6 +50,8 @@ define( isPlaceOrderActionAllowed: ko.observable( quote.billingAddress() != null), comboCardOption: ko.observable('credit'), + checkoutComponent: null, + cardComponent: null, defaults: { template: 'Adyen_Payment/payment/cc-form', @@ -84,18 +86,23 @@ define( let paymentMethodsObserver = adyenPaymentService.getPaymentMethods(); paymentMethodsObserver.subscribe( function (paymentMethodsResponse) { - self.loadCheckoutComponent(paymentMethodsResponse) + self.enablePaymentMethod(paymentMethodsResponse) } ); if(!!paymentMethodsObserver()) { - self.loadCheckoutComponent(paymentMethodsObserver()); + self.enablePaymentMethod(paymentMethodsObserver()); } }, isSchemePaymentsEnabled: function (paymentMethod) { return paymentMethod.type === "scheme"; }, - loadCheckoutComponent: async function (paymentMethodsResponse) { + + /* + * Enables the payment method and sets the required attributes + * if `/paymentMethods` response contains this payment method. + */ + enablePaymentMethod: async function (paymentMethodsResponse) { let self = this; // Check the paymentMethods response to enable Credit Card payments @@ -104,19 +111,46 @@ define( return; } - this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( - paymentMethodsResponse, - this.handleOnAdditionalDetails.bind(this) - ) + self.adyenCCMethod({ + icon: !!paymentMethodsResponse.paymentMethodsExtraDetails.card + ? paymentMethodsResponse.paymentMethodsExtraDetails.card.icon + : undefined + }) + }, + + /* + * Create generic AdyenCheckout library and mount payment method component + * after selecting the payment method via overriding parent `selectPaymentMethod()` function. + */ + selectPaymentMethod: function () { + this._super(); + this.createCheckoutComponent(); + + return true; + }, + /* + * Pre-selected payment methods don't trigger parent's `selectPaymentMethod()` function. + * + * This function is triggered via `afterRender` attribute of the html template + * and creates checkout component for pre-selected payment method. + */ + renderPreSelected: function () { + if (this.isChecked() === this.getCode()) { + this.createCheckoutComponent(); + } + }, + // Build AdyenCheckout library and creates the payment method component + createCheckoutComponent: async function () { + if (!this.checkoutComponent) { + const paymentMethodsResponse = adyenPaymentService.getPaymentMethods(); - if (!!this.checkoutComponent) { - // Setting the icon as an accessible field if it is available - self.adyenCCMethod({ - icon: !!paymentMethodsResponse.paymentMethodsExtraDetails.card - ? paymentMethodsResponse.paymentMethodsExtraDetails.card.icon - : undefined - }) + this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( + paymentMethodsResponse(), + this.handleOnAdditionalDetails.bind(this) + ) } + + this.renderCCPaymentMethod(); }, /** * Returns true if card details can be stored @@ -132,19 +166,31 @@ define( * set up the installments */ renderCCPaymentMethod: function() { + let componentConfig = this.buildComponentConfiguration(); + + this.cardComponent = adyenCheckout.mountPaymentMethodComponent( + this.checkoutComponent, + 'card', + componentConfig, + '#cardContainer' + ) + + return true + }, + + buildComponentConfiguration: function () { let self = this; - if (!self.getClientKey) { + + if (!this.getClientKey) { return false; } - self.installments(0); - - // installments - let allInstallments = self.getAllInstallments(); + this.installments(0); + let allInstallments = this.getAllInstallments(); let componentConfig = { - enableStoreDetails: self.getEnableStoreDetails(), - brands: self.getBrands(), + enableStoreDetails: this.getEnableStoreDetails(), + brands: this.getBrands(), hasHolderName: adyenConfiguration.getHasHolderName(), holderNameRequired: adyenConfiguration.getHasHolderName() && adyenConfiguration.getHolderNameRequired(), @@ -192,21 +238,14 @@ define( } } - if (self.isClickToPayEnabled()) { + if (this.isClickToPayEnabled()) { componentConfig.clickToPayConfiguration = { merchantDisplayName: adyenConfiguration.getMerchantAccount(), - shopperEmail: self.getShopperEmail() + shopperEmail: this.getShopperEmail() }; } - self.cardComponent = adyenCheckout.mountPaymentMethodComponent( - this.checkoutComponent, - 'card', - componentConfig, - '#cardContainer' - ) - - return true + return componentConfig; }, handleAction: function(action, orderId) { @@ -245,13 +284,10 @@ define( * @returns {{method: *}} */ getData: function() { - let stateData = JSON.stringify(this.cardComponent.data); - - window.sessionStorage.setItem('adyen.stateData', stateData); - return { + let data = { 'method': this.item.method, additional_data: { - 'stateData': stateData, + 'stateData': {}, 'guestEmail': quote.guestEmail, 'cc_type': this.creditCardType(), 'combo_card_type': this.comboCardOption(), @@ -259,8 +295,17 @@ define( 'is_active_payment_token_enabler' : this.storeCc, 'number_of_installments': this.installment(), 'frontendType': 'default' - }, + } }; + + // Get state data only if the checkout component is ready, + if (this.checkoutComponent) { + const stateData = JSON.stringify(this.cardComponent.data) + data.additional_data.stateData = stateData; + window.sessionStorage.setItem('adyen.stateData', stateData); + } + + return data; }, /** * Returns state of place order button @@ -501,6 +546,9 @@ define( } return quote.totals().grand_total; }, + getPaymentMethodComponent: function () { + return this.cardComponent; + } }); } ); diff --git a/view/frontend/web/js/view/payment/method-renderer/vault.js b/view/frontend/web/js/view/payment/method-renderer/adyen-cc-vault-method.js similarity index 83% rename from view/frontend/web/js/view/payment/method-renderer/vault.js rename to view/frontend/web/js/view/payment/method-renderer/adyen-cc-vault-method.js index 2757754f2..dbad6e1d5 100644 --- a/view/frontend/web/js/view/payment/method-renderer/vault.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-cc-vault-method.js @@ -49,18 +49,18 @@ define([ return VaultComponent.extend({ defaults: { - template: 'Adyen_Payment/payment/card-vault-form.html', - checkoutComponentBuilt: false, + template: 'Adyen_Payment/payment/cc-vault-form', modalLabel: null, installment: '' }, + checkoutComponent: null, initObservable: function () { this._super() .observe([ - 'checkoutComponentBuilt', 'installment', 'installments', + 'adyenVaultPaymentMethod' ]); return this; @@ -70,27 +70,63 @@ define([ let self = this; this._super(); this.modalLabel = 'card_action_modal_' + this.getId(); + let paymentMethodsObserver = adyenPaymentService.getPaymentMethods(); - paymentMethodsObserver.subscribe( - function (paymentMethodsResponse) { - self.loadCheckoutComponent(paymentMethodsResponse) - }); - self.loadCheckoutComponent(paymentMethodsObserver()); + paymentMethodsObserver.subscribe(function (paymentMethodsResponse) { + self.enablePaymentMethod(paymentMethodsResponse) + }); + + if(!!paymentMethodsObserver()) { + self.enablePaymentMethod(paymentMethodsObserver()); + } return this; }, - loadCheckoutComponent: async function(paymentMethodsResponse) { - this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( - paymentMethodsResponse, - this.handleOnAdditionalDetails.bind(this) - ); + enablePaymentMethod: function (paymentMethodsResponse) { + if (!!paymentMethodsResponse.paymentMethodsResponse) { + this.adyenVaultPaymentMethod(true); + fullScreenLoader.stopLoader(); + } + }, + + /* + * Create generic AdyenCheckout library and mount payment method component + * after selecting the payment method via overriding parent `selectPaymentMethod()` function. + */ + selectPaymentMethod: function () { + this._super(); + this.createCheckoutComponent(); + + return true; + }, - if (this.checkoutComponent) { - this.checkoutComponentBuilt(true) + /* + * Pre-selected payment methods don't trigger parent's `selectPaymentMethod()` function. + * + * This function is triggered via `afterRender` attribute of the html template + * and creates checkout component for pre-selected payment method. + */ + renderPreSelected: function () { + if (this.isChecked() === this.getCode()) { + this.createCheckoutComponent(); } }, + // Build AdyenCheckout library and creates the payment method component + createCheckoutComponent: async function() { + if (!this.checkoutComponent) { + const paymentMethodsResponse = adyenPaymentService.getPaymentMethods(); + + this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( + paymentMethodsResponse(), + this.handleOnAdditionalDetails.bind(this) + ); + } + + this.renderCheckoutComponent(); + }, + handleOnAdditionalDetails: function (result) { let self = this; let request = result.data; @@ -110,7 +146,7 @@ define([ }); }, - renderCardVaultToken: function () { + renderCheckoutComponent: function () { let self = this; if (!this.getClientKey()) { return false diff --git a/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js index e380a6413..aceecc1b9 100755 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-method.js @@ -20,7 +20,8 @@ define( 'Magento_Checkout/js/model/error-processor', 'Adyen_Payment/js/model/adyen-configuration', 'Adyen_Payment/js/model/adyen-payment-modal', - 'Adyen_Payment/js/model/adyen-checkout' + 'Adyen_Payment/js/model/adyen-checkout', + 'Adyen_Payment/js/model/payment-component-states', ], function( ko, @@ -34,21 +35,22 @@ define( errorProcessor, adyenConfiguration, adyenPaymentModal, - adyenCheckout + adyenCheckout, + paymentComponentStates ) { 'use strict'; let popupModal; return Component.extend({ - self: this, - isAvailable: ko.observable(true), - + placeOrderButtonVisible: true, + checkoutComponent: null, + paymentComponent: null, defaults: { template: 'Adyen_Payment/payment/pm-form', orderId: null, modalLabel: 'hpp_actionModal' }, - placeOrderButtonVisible: true, + initObservable: function() { this._super().observe([ 'paymentMethod', @@ -57,6 +59,7 @@ define( 'isPlaceOrderActionAllowed', 'placeOrderAllowed' ]); + return this; }, @@ -64,146 +67,87 @@ define( this._super(); let self = this; - let paymentMethodsObserver = adyenPaymentService.getPaymentMethods(); paymentMethodsObserver.subscribe( function(paymentMethodsResponse) { - self.createCheckoutComponent(paymentMethodsResponse); + self.enablePaymentMethod(paymentMethodsResponse); } ); if(!!paymentMethodsObserver()) { - self.createCheckoutComponent(paymentMethodsObserver()); + self.enablePaymentMethod(paymentMethodsObserver()); } - }, - paymentMethodStates: {}, + paymentComponentStates().initializeState(this.getMethodCode()); + }, - createCheckoutComponent: async function(paymentMethodsResponse) { - // Set to null by default and modify depending on the paymentMethods response - this.adyenPaymentMethod(null); + enablePaymentMethod: function (paymentMethodsResponse) { if (this.checkBrowserCompatibility() && !!paymentMethodsResponse.paymentMethodsResponse) { - this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( - paymentMethodsResponse, - this.handleOnAdditionalDetails.bind(this), - this.handleOnCancel.bind(this), - this.handleOnSubmit.bind(this) + this.paymentMethod( + adyenPaymentService.getPaymentMethodFromResponse( + this.getTxVariant(), + paymentMethodsResponse.paymentMethodsResponse.paymentMethods + ) ); - if (!!this.checkoutComponent) { - this.paymentMethod( - adyenPaymentService.getPaymentMethodFromResponse( - this.getTxVariant(), - paymentMethodsResponse.paymentMethodsResponse.paymentMethods - ) - ); - - if (!!this.paymentMethod()) { - this.paymentMethodsExtraInfo(paymentMethodsResponse.paymentMethodsExtraDetails); - // Setting the icon and method txvariant as an accessible field if it is available - this.adyenPaymentMethod({ - icon: !!paymentMethodsResponse.paymentMethodsExtraDetails[this.getTxVariant()] - ? paymentMethodsResponse.paymentMethodsExtraDetails[this.getTxVariant()].icon - : undefined, - method: this.getTxVariant() - }); - } + if (!!this.paymentMethod()) { + this.paymentMethodsExtraInfo(paymentMethodsResponse.paymentMethodsExtraDetails); + // Setting the icon and method txvariant as an accessible field if it is available + this.adyenPaymentMethod({ + icon: !!paymentMethodsResponse.paymentMethodsExtraDetails[this.getTxVariant()] + ? paymentMethodsResponse.paymentMethodsExtraDetails[this.getTxVariant()].icon + : undefined, + method: this.getTxVariant() + }); } - } - - fullScreenLoader.stopLoader(); - }, - handleOnSubmit: async function(state, component) { - if (this.validate()) { - let data = {}; - data.method = this.getCode(); - - let additionalData = {}; - let stateData = component.data; - additionalData.stateData = JSON.stringify(stateData); - data.additional_data = additionalData; - - await this.placeRedirectOrder(data, component); + fullScreenLoader.stopLoader(); } - - return false; }, - handleOnCancel: function(state, component) { - const self = this; + /* + * Create generic AdyenCheckout library and mount payment method component + * after selecting the payment method via overriding parent `selectPaymentMethod()` function. + */ + selectPaymentMethod: function () { + this._super(); + this.createCheckoutComponent(); + return true; + }, - // call endpoint with state.data if available - let request = {}; - if (!!state.data) { - request = state.data; + /* + * Pre-selected payment methods don't trigger parent's `selectPaymentMethod()` function. + * + * This function is triggered via `afterRender` attribute of the html template + * and creates checkout component for pre-selected payment method. + */ + renderPreSelected: async function () { + if (this.isChecked() === this.getCode()) { + await this.createCheckoutComponent(); } + }, - request.cancelled = true; + // Build AdyenCheckout library and creates the payment method component + createCheckoutComponent: async function() { + if (!this.checkoutComponent) { + const paymentMethodsResponse = adyenPaymentService.getPaymentMethods(); - adyenPaymentService.paymentDetails(request, self.orderId).done(function() { - $.mage.redirect( - window.checkoutConfig.payment.adyen.successPage + this.checkoutComponent = await adyenCheckout.buildCheckoutComponent( + paymentMethodsResponse(), + this.handleOnAdditionalDetails.bind(this), + this.handleOnCancel.bind(this), + this.handleOnSubmit.bind(this) ); - }).fail(function(response) { - fullScreenLoader.stopLoader(); - if (self.popupModal) { - self.closeModal(self.popupModal); - } - errorProcessor.process(response, - self.currentMessageContainer); - self.isPlaceOrderAllowed(true); - self.showErrorMessage(response); - }); - }, - handleOnAdditionalDetails: function(state, component) { - const self = this; - // call endpoint with state.data if available - let request = {}; - if (!!state.data) { - request = state.data; + this.renderCheckoutComponent(); } - - adyenPaymentService.paymentDetails(request, self.orderId).done(function() { - $.mage.redirect( - window.checkoutConfig.payment.adyen.successPage, - ); - }).fail(function(response) { - fullScreenLoader.stopLoader(); - if (self.popupModal) { - self.closeModal(self.popupModal); - } - errorProcessor.process(response, - self.currentMessageContainer); - self.isPlaceOrderAllowed(true); - }); }, + renderCheckoutComponent: function() { let methodCode = this.getMethodCode(); - let configuration = this.buildComponentConfiguration(this.paymentMethod(), this.paymentMethodsExtraInfo()); this.mountPaymentMethodComponent(this.paymentMethod(), configuration, methodCode); - - setTimeout(() => { - this.updatePlaceOrderButtonState(methodCode); - }, 0); - }, - updatePlaceOrderButtonState: function(methodCode) { - let state = this.initializeMethod(methodCode); - let container = $(`#${methodCode}Container`); - - // Check if the payment method has any input fields - let hasForm = container.find('input, select, textarea').length > 0; - - if (hasForm) { - // If there's a form, start with button disabled and let the onChange handler manage its state - state.isPlaceOrderAllowed(false); - } else { - // If there's no form, it's likely a direct redirect method, so enable the button - state.isPlaceOrderAllowed(true); - } }, buildComponentConfiguration: function (paymentMethod, paymentMethodsExtraInfo) { @@ -227,34 +171,30 @@ define( countryCode: formattedShippingAddress.country ? formattedShippingAddress.country : formattedBillingAddress.country, // Use shipping address details as default and fall back to billing address if missing data: {}, onChange: function (state) { - self.isPlaceOrderAllowed(state.isValid); + paymentComponentStates().setIsPlaceOrderAllowed(self.getMethodCode(), state.isValid); }, }); return configuration; }, - getTxVariant: function () { - return window.checkoutConfig.payment.adyen.txVariants[this.getCode()]; - }, - - getMethodCode: function () { - return this.item.method; - }, - mountPaymentMethodComponent: function(paymentMethod, configuration, methodCode) { - let self = this; - let state = this.initializeMethod(methodCode); - try { const containerId = '#' + paymentMethod.type + 'Container'; - state.paymentComponent = adyenCheckout.mountPaymentMethodComponent( - self.checkoutComponent, - self.getTxVariant(), + this.paymentComponent = adyenCheckout.mountPaymentMethodComponent( + this.checkoutComponent, + this.getTxVariant(), configuration, containerId ); + + if (this.paymentComponent) { + paymentComponentStates().setIsPlaceOrderAllowed( + methodCode, + this.paymentComponent.isValid + ); + } } catch (err) { if ('test' === adyenConfiguration.getCheckoutEnvironment()) { console.error(err); @@ -262,81 +202,87 @@ define( } }, - validate: function() { - let state = this.initializeMethod(this.getMethodCode()); - - if (!state.paymentComponent) { - return true; - } + handleOnSubmit: async function(state, component) { + if (this.validate()) { + let data = {}; + data.method = this.getCode(); - state.paymentComponent.showValidation(); + let additionalData = {}; + let stateData = component.data; + additionalData.stateData = JSON.stringify(stateData); + data.additional_data = additionalData; - if (!this.isComponentValid(state.paymentComponent)) { - return false; + await this.placeRedirectOrder(data, component); } - const form = '#adyen-' + this.getTxVariant() + '-form'; - return $(form).validation() && $(form).validation('isValid') && additionalValidators.validate(); - }, - - isComponentValid: function(component) { - return component.state.isValid !== false && - !this.isPaymentDataEmpty(component.state) && - !this.isPaymentDataEmpty(component.data); - }, - - isPaymentDataEmpty: function(obj) { - if (obj && obj.data && typeof obj.data === 'object' && Object.keys(obj.data).length > 0) { - return Object.values(obj.data).every(value => - value === '' || (typeof value === 'object' && Object.keys(value).length === 0) - ); - } return false; }, + handleOnAdditionalDetails: function(state, component) { + const self = this; + // call endpoint with state.data if available + let request = {}; + if (!!state.data) { + request = state.data; + } - showErrorMessage: function(message) { - messageList.addErrorMessage({ - message: message + adyenPaymentService.paymentDetails(request, self.orderId).done(function() { + $.mage.redirect( + window.checkoutConfig.payment.adyen.successPage, + ); + }).fail(function(response) { + fullScreenLoader.stopLoader(); + if (self.popupModal) { + self.closeModal(self.popupModal); + } + errorProcessor.process(response, + self.currentMessageContainer); + paymentComponentStates().setIsPlaceOrderAllowed(self.getMethodCode(), true); }); }, + handleOnCancel: function(state, component) { + const self = this; - initializeMethod: function(methodCode) { - if (!this.paymentMethodStates[methodCode]) { - this.paymentMethodStates[methodCode] = { - isPlaceOrderAllowed: ko.observable(true), - paymentComponent: null - }; + // call endpoint with state.data if available + let request = {}; + if (!!state.data) { + request = state.data; } - return this.paymentMethodStates[methodCode]; - }, - isPlaceOrderAllowed: function(methodCode) { - let state = this.initializeMethod(methodCode); - return state.isPlaceOrderAllowed; - }, + request.cancelled = true; - setPlaceOrderAllowed: function(methodCode, allowed) { - let state = this.initializeMethod(methodCode); - state.isPlaceOrderAllowed(allowed); + adyenPaymentService.paymentDetails(request, self.orderId).done(function() { + $.mage.redirect( + window.checkoutConfig.payment.adyen.successPage + ); + }).fail(function(response) { + fullScreenLoader.stopLoader(); + if (self.popupModal) { + self.closeModal(self.popupModal); + } + errorProcessor.process(response, self.currentMessageContainer); + paymentComponentStates().setIsPlaceOrderAllowed(self.getMethodCode(), true); + self.showErrorMessage(response); + }); }, - showPlaceOrderButton: function() { - return this.placeOrderButtonVisible; + handleOnFailure: function(response, component) { + paymentComponentStates().setIsPlaceOrderAllowed(this.getMethodCode(), true); + fullScreenLoader.stopLoader(); + errorProcessor.process(response, this.currentMessageContainer); }, placeOrder: function() { let methodCode = this.getMethodCode(); - let state = this.initializeMethod(methodCode); - if (state.paymentComponent) { - state.paymentComponent.showValidation(); + if (this.paymentComponent) { + this.paymentComponent.showValidation(); } if (this.validate()) { let data = { - 'method': this.item.method + 'method': methodCode }; let additionalData = {}; @@ -344,8 +290,8 @@ define( additionalData.frontendType = 'luma'; let stateData; - if (state.paymentComponent) { - stateData = state.paymentComponent.data; + if (this.paymentComponent) { + stateData = this.paymentComponent.data; } else { stateData = { paymentMethod: { @@ -357,9 +303,9 @@ define( additionalData.stateData = JSON.stringify(stateData); data.additional_data = additionalData; - this.placeRedirectOrder(data, state.paymentComponent); + this.placeRedirectOrder(data, this.paymentComponent); } else { - this.isPlaceOrderAllowed(true); + paymentComponentStates().setIsPlaceOrderAllowed(methodCode, true); } return false; @@ -370,7 +316,7 @@ define( fullScreenLoader.startLoader(); $('.hpp-message').slideUp(); - self.isPlaceOrderAllowed(false); + paymentComponentStates().setIsPlaceOrderAllowed(this.getMethodCode(), false); try { const orderId = await placeOrderAction(data, self.currentMessageContainer); @@ -382,11 +328,28 @@ define( } }, + getTxVariant: function () { + return window.checkoutConfig.payment.adyen.txVariants[this.getCode()]; + }, + + getMethodCode: function () { + return this.item.method; + }, - handleOnFailure: function(response, component) { - this.isPlaceOrderAllowed(true); - fullScreenLoader.stopLoader(); - errorProcessor.process(response, this.currentMessageContainer); + validate: function() { + const form = '#adyen-' + this.getTxVariant() + '-form'; + const validate = $(form).validation() && $(form).validation('isValid'); + return validate && additionalValidators.validate(); + }, + + showErrorMessage: function(message) { + messageList.addErrorMessage({ + message: message + }); + }, + + showPlaceOrderButton: function() { + return this.placeOrderButtonVisible; }, /** @@ -432,8 +395,7 @@ define( isButtonActive: function() { - let methodCode = this.getMethodCode(); - return this.isPlaceOrderAllowed(methodCode); + return paymentComponentStates().getIsPlaceOrderAllowed(this.getMethodCode()); }, /** @@ -502,6 +464,10 @@ define( checkBrowserCompatibility: function () { return true; + }, + + getPaymentMethodComponent: function () { + return this.paymentComponent; } }); }, diff --git a/view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-vault-method.js similarity index 94% rename from view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js rename to view/frontend/web/js/view/payment/method-renderer/adyen-pm-vault-method.js index 398aaa13d..545231318 100644 --- a/view/frontend/web/js/view/payment/method-renderer/payment_method_vault.js +++ b/view/frontend/web/js/view/payment/method-renderer/adyen-pm-vault-method.js @@ -17,7 +17,7 @@ define([ return VaultComponent.extend({ defaults: { - template: 'Adyen_Payment/payment/payment-method-vault-form' + template: 'Adyen_Payment/payment/pm-vault-form' }, /** * Get tx_variant 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 deleted file mode 100644 index 56b4b728b..000000000 --- a/view/frontend/web/js/view/payment/method-renderer/adyen-vault-method.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * - * 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' - }, - /** - * 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/multishipping/adyen-cc-method.js b/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-cc-method.js index 24d0986a5..635a91aed 100644 --- a/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-cc-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-cc-method.js @@ -13,62 +13,82 @@ define([ 'jquery', 'ko', 'Adyen_Payment/js/view/payment/method-renderer/adyen-cc-method', - 'Adyen_Payment/js/model/adyen-configuration', - 'Adyen_Payment/js/model/adyen-payment-service', - 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Checkout/js/model/full-screen-loader' ], function ( $, ko, Component, - adyenConfiguration, - adyenPaymentService, fullScreenLoader ) { 'use strict'; return Component.extend({ + paymentMethodReady: ko.observable(false), + isTemplateRendered: ko.observable(false), defaults: { template: 'Adyen_Payment/payment/multishipping/cc-form' }, - paymentMethodReady: ko.observable(false), + enablePaymentMethod: async function (paymentMethodsResponse) { + this.paymentMethodReady(paymentMethodsResponse); + }, - initialize: function() { + selectPaymentMethod: function () { + fullScreenLoader.startLoader(); let self = this; - this._super(); - let paymentMethodsObserver = adyenPaymentService.getPaymentMethods(); - paymentMethodsObserver.subscribe( - function(paymentMethods) { - self.paymentMethodReady(paymentMethods); - } - ); + this.isTemplateRendered.subscribe(function (response) { + self.initializeMultishippingPaymentMethod(); + }) + if (this.isTemplateRendered()) { + this.initializeMultishippingPaymentMethod(); + } - this.paymentMethodReady(paymentMethodsObserver()); + return true; }, - selectPaymentMethod: function () { - this.renderSecureFields(); - return this._super(); + // This will return a promise once the payment component is created and mounted. + createMultishippingCheckoutComponent: async function () { + await this.createCheckoutComponent(); + return true; }, - renderSecureFields: function () { + initializeMultishippingPaymentMethod: function () { let self = this; - if (!self.getClientKey) { - return; - } - self.cardComponent = self.checkoutComponent.create('card', { - enableStoreDetails: self.getEnableStoreDetails(), - brands: self.getBrands(), - hasHolderName: adyenConfiguration.getHasHolderName(), - holderNameRequired: adyenConfiguration.getHasHolderName() && - adyenConfiguration.getHolderNameRequired(), - onChange: function (state) { - $('#stateData').val(state.isValid ? JSON.stringify(state.data) : ''); - self.placeOrderAllowed(!!state.isValid); - self.storeCc = !!state.data.storePaymentMethod; + this.createMultishippingCheckoutComponent().then(function (status) { + if (status) { + let paymentComponent = self.getPaymentMethodComponent(); + + // Remove previously assigned event listeners + $("#payment-continue").off(); + // Assign event listener for component validation + $("#payment-continue").on("click", function () { + paymentComponent.showValidation(); + }); + } else { + console.warn('Payment component could not be generated!'); } - }).mount('#cardContainer'); + + fullScreenLoader.stopLoader(); + }); + }, + + buildComponentConfiguration: function () { + let self = this; + let baseComponentConfiguration = this._super(); + + baseComponentConfiguration.onChange = function (state) { + $('#stateData').val(state.isValid ? JSON.stringify(state.data) : ''); + self.placeOrderAllowed(!!state.isValid); + self.storeCc = !!state.data.storePaymentMethod; + }; + + return baseComponentConfiguration; + }, + + // Observable is set to true after div element in `cc-form.html` template is rendered + setIsTemplateRendered: function () { + this.isTemplateRendered(true); } }); }); diff --git a/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-pm-method.js b/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-pm-method.js index ba35383a7..7559a663e 100644 --- a/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-pm-method.js +++ b/view/frontend/web/js/view/payment/method-renderer/multishipping/adyen-pm-method.js @@ -14,114 +14,87 @@ define([ 'ko', 'Adyen_Payment/js/view/payment/method-renderer/adyen-pm-method', 'Adyen_Payment/js/helper/configHelper', - 'Adyen_Payment/js/model/adyen-payment-service' + 'Adyen_Payment/js/model/adyen-payment-service', + 'Magento_Checkout/js/model/full-screen-loader' ], function ( $, ko, Component, configHelper, - adyenPaymentService + adyenPaymentService, + fullScreenLoader ) { 'use strict'; return Component.extend({ + paymentMethodReady: ko.observable(false), + isTemplateRendered: ko.observable(false), defaults: { template: 'Adyen_Payment/payment/multishipping/pm-form' }, - paymentMethodReady: ko.observable(false), - - initialize: function() { - let self = this; + enablePaymentMethod: function (paymentMethodsResponse) { this._super(); - - this.isChecked = ko.observable(false); - - let paymentMethodsObserver = adyenPaymentService.getPaymentMethods(); - paymentMethodsObserver.subscribe( - function(paymentMethods) { - self.paymentMethodReady(paymentMethods); - self.renderCheckoutComponent(); - } - ); - - this.paymentMethodReady(paymentMethodsObserver()); + this.paymentMethodReady(paymentMethodsResponse); }, selectPaymentMethod: function () { - let result = this._super(); - this.isChecked(true); - this.renderCheckoutComponent(); + fullScreenLoader.startLoader(); + let self = this; - if (!!this.paymentComponent && this.paymentComponent.isValid) { - $('#stateData').val(JSON.stringify(this.paymentComponent.data)); - } else { - console.warn('Payment component is not valid or not available'); + // Only try to mount component if HTML template is rendered. + this.isTemplateRendered.subscribe(function (response) { + self.initializeMultishippingPaymentMethod(); + }); + if (this.isTemplateRendered()) { + self.initializeMultishippingPaymentMethod(); } - return result; - }, - - buildComponentConfiguration: function(paymentMethod, paymentMethodsExtraInfo) { - return configHelper.buildMultishippingComponentConfiguration(paymentMethod, paymentMethodsExtraInfo); + return true; }, - renderCheckoutComponent: function() { - let methodCode = this.getMethodCode(); - let paymentMethod = this.paymentMethod(); - - if (!paymentMethod || !this.isChecked()) { - console.error('Payment method is undefined for ', methodCode); - return; - } - - let configuration = this.buildComponentConfiguration(paymentMethod, this.paymentMethodsExtraInfo()); - - if (this.paymentComponent) { - this.paymentComponent.update(configuration); - } else { - this.mountPaymentMethodComponent(paymentMethod, configuration, methodCode); - } + // This will return a promise once the payment component is created and mounted. + createMultishippingCheckoutComponent: async function () { + await this.createCheckoutComponent(); + return true; }, - mountPaymentMethodComponent: function(paymentMethod, configuration, methodCode) { + initializeMultishippingPaymentMethod: function () { let self = this; - const containerId = '#' + paymentMethod.type + 'Container'; - - if ($(containerId).length) { - if (this.paymentComponent && typeof this.paymentComponent.unmount === 'function') { - this.paymentComponent.unmount(); - } - - const paymentMethodComponent = this.checkoutComponent.create( - paymentMethod.type, - configuration - ); - - - paymentMethodComponent.mount(containerId); - - this.paymentComponent = paymentMethodComponent; - - if (typeof paymentMethodComponent.onChange === 'function') { - paymentMethodComponent.onChange(function(state) { - self.onPaymentMethodChange(state, methodCode); + /* + * Wait until payment component is created and mounted. + * Then, handle the promise, fetch the stateData and fill out the hidden input field. + */ + this.createMultishippingCheckoutComponent().then(function (status) { + if (status) { + let paymentComponent = self.getPaymentMethodComponent(); + + if (paymentComponent && paymentComponent.isValid) { + $('#stateData').val(JSON.stringify(paymentComponent.data)); + } + + // Remove previously assigned event listeners + $("#payment-continue").off(); + // Assign event listener for component validation + $("#payment-continue").on("click", function () { + paymentComponent.showValidation(); }); } else { - console.warn('Unable to add onChange event listener to payment component', paymentMethodComponent); + console.warn('Payment component could not be generated!'); } - } else { - console.warn('Container not found for', containerId); - } + + fullScreenLoader.stopLoader(); + }); }, - onPaymentMethodChange: function(state, methodCode) { - if (methodCode !== this.getMethodCode()) { - return; - } - this.isPlaceOrderAllowed(state.isValid); - $('#stateData').val(state.data ? JSON.stringify(state.data) : ''); + buildComponentConfiguration: function(paymentMethod, paymentMethodsExtraInfo) { + return configHelper.buildMultishippingComponentConfiguration(paymentMethod, paymentMethodsExtraInfo); + }, + + // Observable is set to true after div element in `pm-form.html` template is rendered + setIsTemplateRendered: function () { + this.isTemplateRendered(true); } }); }); diff --git a/view/frontend/web/template/payment/boleto-form.html b/view/frontend/web/template/payment/boleto-form.html deleted file mode 100755 index c6b3d670f..000000000 --- a/view/frontend/web/template/payment/boleto-form.html +++ /dev/null @@ -1,159 +0,0 @@ - - - -
-
- - -
-
-
- - - -
- - - -
- - - - - - -
- - - -
- - -
- -
- - -
-
- - - -
- -
- -
-
- - - -
- -
- - -
-
- -
- -
- - -
-
- -
- -
- - - -
- -
-
- -
-
-
-
-
- diff --git a/view/frontend/web/template/payment/cc-form.html b/view/frontend/web/template/payment/cc-form.html index 2e2d2790b..4fb8ae5fa 100755 --- a/view/frontend/web/template/payment/cc-form.html +++ b/view/frontend/web/template/payment/cc-form.html @@ -85,7 +85,7 @@
-
+
diff --git a/view/frontend/web/template/payment/card-vault-form.html b/view/frontend/web/template/payment/cc-vault-form.html similarity index 97% rename from view/frontend/web/template/payment/card-vault-form.html rename to view/frontend/web/template/payment/cc-vault-form.html index 31327f7b2..ddc824d3c 100644 --- a/view/frontend/web/template/payment/card-vault-form.html +++ b/view/frontend/web/template/payment/cc-vault-form.html @@ -10,7 +10,7 @@ */ --> - +
-
+
diff --git a/view/frontend/web/template/payment/multishipping/cc-form.html b/view/frontend/web/template/payment/multishipping/cc-form.html index 6f0ca3970..8672e2d7b 100644 --- a/view/frontend/web/template/payment/multishipping/cc-form.html +++ b/view/frontend/web/template/payment/multishipping/cc-form.html @@ -1,4 +1,6 @@
-
+
diff --git a/view/frontend/web/template/payment/multishipping/pm-form.html b/view/frontend/web/template/payment/multishipping/pm-form.html index 1c65d60ac..66c6a4b7d 100644 --- a/view/frontend/web/template/payment/multishipping/pm-form.html +++ b/view/frontend/web/template/payment/multishipping/pm-form.html @@ -1 +1,4 @@ -
+
+
diff --git a/view/frontend/web/template/payment/payment-method-vault.html b/view/frontend/web/template/payment/payment-method-vault.html deleted file mode 100644 index 1a120b32b..000000000 --- a/view/frontend/web/template/payment/payment-method-vault.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
- - -
- -
-
-
- -
-
-
-
diff --git a/view/frontend/web/template/payment/pm-form.html b/view/frontend/web/template/payment/pm-form.html index bd6be127a..253083f35 100755 --- a/view/frontend/web/template/payment/pm-form.html +++ b/view/frontend/web/template/payment/pm-form.html @@ -63,7 +63,7 @@
-
+
diff --git a/view/frontend/web/template/payment/payment-method-vault-form.html b/view/frontend/web/template/payment/pm-vault-form.html similarity index 100% rename from view/frontend/web/template/payment/payment-method-vault-form.html rename to view/frontend/web/template/payment/pm-vault-form.html