From feba306bca32234cf0179ee6a6d007ba5a74621c Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 23 Jan 2024 15:51:28 +0200 Subject: [PATCH 1/3] Added card-fields component --- .../common/card-fields.component.js | 331 ++++++++++++++++++ .../common/payment-option.component.js | 41 ++- _dev/js/front/src/service/paypal.service.js | 82 ++++- .../default-selectors-ps1_6.js | 15 +- .../default-selectors-ps1_7.js | 15 +- .../service/query-selector.service/index.js | 41 +++ .../query-selector-ps1_6.service.js | 60 ++++ .../query-selector-ps1_7.service.js | 60 ++++ ps_checkout.php | 2 + views/css/payments.css | 17 + views/css/payments16.css | 17 + views/templates/hook/displayPayment.tpl | 36 +- 12 files changed, 676 insertions(+), 41 deletions(-) create mode 100644 _dev/js/front/src/components/common/card-fields.component.js diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js new file mode 100644 index 000000000..bc9603852 --- /dev/null +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -0,0 +1,331 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ + +/** + * @typedef PaypalCardFieldCardField + * @type {*} + * + * @property {boolean} isEmpty + * @property {boolean} isValid + * @property {boolean} isPotentiallyValid + * @property {boolean} isFocused + */ + +/** + * @typedef PaypalCardFieldsEvent + * @type {*} + * + * @property {string} emittedBy + * @property {boolean} isFormValid + * @property {String[]} errors + * @property {*} fields + * @property {PaypalCardFieldCardField} fields.cardCvvField + * @property {PaypalCardFieldCardField} fields.cardExpiryField + * @property {PaypalCardFieldCardField} fields.cardNameField + * @property {PaypalCardFieldCardField} fields.cardNumberField + */ + +import { BaseComponent } from '../../core/dependency-injection/base.component'; + +export class CardFieldsComponent extends BaseComponent { + static Inject = { + config: 'PsCheckoutConfig', + configPayPal: 'PayPalSdkConfig', + payPalService: 'PayPalService', + psCheckoutApi: 'PsCheckoutApi', + psCheckoutService: 'PsCheckoutService', + querySelectorService: 'QuerySelectorService', + }; + + created() { + this.data.name = this.props.fundingSource.name; + this.data.validity = false; + /** + * @property {PaypalCardFieldsEvent} data.cardFieldsState + */ + this.data.cardFieldsState = {}; + + this.data.cardFieldsFocused = { + name: false, + number: false, + expiry: false, + cvv: false, + } + this.data.HTMLElement = this.props.HTMLElement; + this.data.HTMLElementCardForm = this.querySelectorService.getCardFieldsFormContainer(); + this.data.HTMLElementBaseButton = this.getBaseButton(); + this.data.HTMLElementButton = null; + this.data.HTMLElementButtonWrapper = this.getButtonWrapper(); + + this.data.HTMLElementCardHolderName = this.querySelectorService.getCardFieldsNameInputContainer(); + this.data.HTMLElementCardNumber = this.querySelectorService.getCardFieldsNumberInputContainer(); + this.data.HTMLElementCardExpiry = this.querySelectorService.getCardFieldsExpiryInputContainer(); + this.data.HTMLElementCardCvv = this.querySelectorService.getCardFieldsCvvInputContainer(); + + this.data.HTMLElementCardNameError= this.querySelectorService.getCardFieldsNameError(); + this.data.HTMLElementCardNumberError= this.querySelectorService.getCardFieldsNumberError(); + this.data.HTMLElementCardVendorError= this.querySelectorService.getCardFieldsVendorError(); + this.data.HTMLElementCardExpiryError= this.querySelectorService.getCardFieldsExpiryError(); + this.data.HTMLElementCardCvvError= this.querySelectorService.getCardFieldsCvvError(); + } + + getBaseButton() { + const buttonSelector = `#payment-confirmation button`; + return document.querySelector(buttonSelector); + } + + getButtonWrapper() { + const buttonWrapper = `.ps_checkout-button[data-funding-source=${this.data.name}]`; + return document.querySelector(buttonWrapper); + } + + isSubmittable() { + return this.data.conditions + ? this.data.conditions.isChecked() && this.data.validity + : this.data.validity; + } + + isFormValid() { + const {cardNameField, cardNumberField, cardExpiryField, cardCvvField } = this.data.cardFieldsState.fields; + return (cardNameField.isEmpty || cardNameField.isValid) && + cardNumberField.isValid && + cardExpiryField.isValid && + cardCvvField.isValid + } + + setFocusedField(fieldName) { + this.data.cardFieldsFocused[fieldName] = true; + } + + toggleCardNameFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardNameField; + const hideError = isEmpty || isFocused || isValid; + + this.data.HTMLElementCardNameError.classList.toggle('hidden', hideError) + } + + toggleCardNumberFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardNumberField; + const hideError = isFocused || !this.data.cardFieldsFocused.number || isValid; + + this.data.HTMLElementCardNumberError.classList.toggle('hidden', !isPotentiallyValid || hideError) + this.data.HTMLElementCardVendorError.classList.toggle('hidden', isPotentiallyValid) + } + + toggleCardExpiryFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardExpiryField; + const hideError = isPotentiallyValid && (isFocused || !this.data.cardFieldsFocused.expiry || isValid); + + this.data.HTMLElementCardExpiryError.classList.toggle('hidden', hideError) + } + toggleCardCvvFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardCvvField; + const hideError = isFocused || !this.data.cardFieldsFocused.cvv || isValid; + + this.data.HTMLElementCardCvvError.classList.toggle('hidden', hideError) + } + + toggleCardFieldsErrors() { + this.toggleCardNameFieldError(); + this.toggleCardNumberFieldError(); + this.toggleCardExpiryFieldError(); + this.toggleCardCvvFieldError(); + } + + /** + * @param {PaypalCardFieldsEvent} event + */ + updateCardFieldsState(event) { + this.setFocusedField(event.emittedBy); + this.data.cardFieldsState = event; + this.data.validity = this.isFormValid(); + + this.isSubmittable() + ? this.data.HTMLElementButton.removeAttribute('disabled') + : this.data.HTMLElementButton.setAttribute('disabled', ''); + + this.toggleCardFieldsErrors(); + } + + renderPayPalCardFields() { + this.data.HTMLElementCardForm.classList.toggle('loading', true); + + const style = { + ...{ + input: { + 'font-size': '17px', + 'font-family': 'helvetica, tahoma, calibri, sans-serif', + color: '#3a3a3a', + padding: '8px 10px' + }, + ':focus': { + color: 'black' + }, + body: { + padding: '0px' + } + }, + ...(this.configPayPal.hostedFieldsCustomization || {}), + ...(window.ps_checkout.hostedFieldsCustomization || {}) + }; + + this.payPalService + .getCardFields( + { + name: this.data.HTMLElementCardHolderName, + number: this.data.HTMLElementCardNumber, + cvv: this.data.HTMLElementCardCvv, + expiry: this.data.HTMLElementCardExpiry + }, + { + style, + createOrder: async (data) => { + this.data.HTMLElementButton.setAttribute('disabled', true); + + return this.psCheckoutApi + .postCreateOrder({ + ...data, + fundingSource: this.data.name, + isCardFields: true, + // vault: storeCardInVault + }) + .then(data => { + return data; + }) + .catch(error => { + this.data.notification.showError( + `${error.message} ${error.name}` + ); + }) + }, + onApprove: async (data) => { + return this.psCheckoutApi.postValidateOrder({ + ...data, + fundingSource: this.data.name, + isHostedFields: true + }) + .catch(error => { + let message = error.message || ''; + + if (!message) { + message = `Unknown error, code: ${error.code || 'none'}, description: ${error.description || 'none'}`; + } + + this.data.loader.hide(); + this.data.notification.showError(message); + this.data.HTMLElementButton.removeAttribute('disabled'); + }) + }, + onError: async (error) => { + this.data.loader.hide(); + let message = error.message || ''; + this.data.notification.showError(message); + this.data.HTMLElementButton.removeAttribute('disabled'); + }, + inputEvents: { + /** + * @param {PaypalCardFieldsEvent} event + */ + onChange: (event) => { + this.updateCardFieldsState(event); + this.data.cardFields = event; + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onFocus: (event) => { + this.updateCardFieldsState(event); + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsFocus', { + detail: { ps_checkout: window.ps_checkout, event } + }) + ); + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onBlur: (event) => { + this.updateCardFieldsState(event); + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsBlur', { + detail: { ps_checkout: window.ps_checkout, event } + }) + ); + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onInputSubmitRequest: (event) => { + this.updateCardFieldsState(event); + }, + } + + }, + ) + .then(cardFields => { + this.data.HTMLElementCardForm.classList.toggle('loading', false); + if (this.data.HTMLElement !== null) { + this.data.HTMLElementButton.addEventListener('click', event => { + event.preventDefault(); + this.data.loader.show(); + // this.data.HTMLElementButton.classList.toggle('disabled', true); + this.data.HTMLElementButton.setAttribute('disabled', ''); + + cardFields.submit(); + }); + } + }); + } + + + + renderButton() { + this.data.HTMLElementButton = this.data.HTMLElementBaseButton.cloneNode( + true + ); + + this.data.HTMLElementButtonWrapper.append(this.data.HTMLElementButton); + this.data.HTMLElementButton.classList.remove('disabled'); + this.data.HTMLElementButton.style.display = ''; + this.data.HTMLElementButton.disabled = !this.isSubmittable(); + + this.data.conditions && + this.data.conditions.onChange(() => { + // In some PS versions, the handler fails to disable the button because of the timing. + setTimeout(() => { + this.data.HTMLElementButton.disabled = !this.isSubmittable(); + }, 0); + }); + } + + render() { + this.data.conditions = this.app.root.children.conditionsCheckbox; + this.data.notification = this.app.root.children.notification; + this.data.loader = this.app.root.children.loader; + + this.renderButton(); + this.renderPayPalCardFields(); + + return this; + } +} diff --git a/_dev/js/front/src/components/common/payment-option.component.js b/_dev/js/front/src/components/common/payment-option.component.js index c207c5c51..9148ec9b0 100644 --- a/_dev/js/front/src/components/common/payment-option.component.js +++ b/_dev/js/front/src/components/common/payment-option.component.js @@ -18,10 +18,10 @@ */ import { BaseComponent } from '../../core/dependency-injection/base.component'; -import { HostedFieldsComponent } from './hosted-fields.component'; import { MarkComponent } from './marker.component'; import { SmartButtonComponent } from './smart-button.component'; import { PaymentFieldsComponent } from "./payment-fields.component"; +import {CardFieldsComponent} from "./card-fields.component"; /** * @typedef PaymentOptionComponentProps @@ -36,6 +36,7 @@ export class PaymentOptionComponent extends BaseComponent { static Inject = { config: 'PsCheckoutConfig', payPalService: 'PayPalService', + querySelectorService: 'QuerySelectorService', $: '$' }; @@ -47,7 +48,7 @@ export class PaymentOptionComponent extends BaseComponent { this.data.HTMLElementLabel = this.getLabel(); this.data.HTMLElementMark = this.props.HTMLElementMark || null; - this.data.HTMLElementHostedFields = this.getHostedFields(); + this.data.HTMLElementCardFields = this.querySelectorService.getCardFieldsFormContainer(); this.data.HTMLElementSmartButton = this.getSmartButton(); this.data.HTMLElementPaymentFields = this.getPaymentFields(); } @@ -57,16 +58,6 @@ export class PaymentOptionComponent extends BaseComponent { return document.getElementById(wrapperId); } - getHostedFields() { - const hostedFieldsFormId = 'ps_checkout-hosted-fields-form'; - - return ( - this.data.name === 'card' - && this.config.hostedFieldsEnabled - && document.getElementById(hostedFieldsFormId) - ); - } - getPaymentFields() { const container = `pay-with-${this.data.HTMLElement.id}-form`; const APM = ['bancontact', 'blik', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort']; @@ -160,16 +151,24 @@ export class PaymentOptionComponent extends BaseComponent { this.renderMark(); this.renderPaymentFields(); - let isHostedFieldsEligible = this.payPalService.isHostedFieldsEligible(); - if (this.data.HTMLElementHostedFields && !isHostedFieldsEligible) { - this.data.HTMLElementHostedFields.style.display = 'none'; + const isCardFieldsEligible = this.payPalService.isCardFieldsEligible(); + // Check if all fields required for cardFields are present in DOM + const isCardFieldsAvailable = this.data.name === 'card' + && this.config.hostedFieldsEnabled + && this.querySelectorService.getCardFieldsNameInputContainer() + && this.querySelectorService.getCardFieldsNumberInputContainer() + && this.querySelectorService.getCardFieldsExpiryInputContainer() + && this.querySelectorService.getCardFieldsCvvInputContainer(); + + if (this.data.HTMLElementCardFields && (!isCardFieldsEligible || !isCardFieldsAvailable)) { + this.data.HTMLElementCardFields.style.display = 'none'; } - if (this.data.HTMLElementHostedFields && isHostedFieldsEligible) { - this.children.hostedFields = new HostedFieldsComponent(this.app, { + if (this.data.HTMLElementCardFields && isCardFieldsEligible && isCardFieldsAvailable) { + this.data.HTMLElementCardFields.style.display = ''; + this.children.cardFields = new CardFieldsComponent(this.app, { fundingSource: this.props.fundingSource, - - HTMLElement: this.data.HTMLElementHostedFields + HTMLElement: this.data.HTMLElementCardFields }).render(); } else { this.children.smartButton = new SmartButtonComponent(this.app, { @@ -185,8 +184,8 @@ export class PaymentOptionComponent extends BaseComponent { fundingSource: this.data.name, HTMLElement: this.data.HTMLElement, HTMLElementContainer: this.data.HTMLElementContainer, - HTMLElementBinary: this.data.HTMLElementHostedFields && isHostedFieldsEligible - ? this.children.hostedFields.data.HTMLElementButton.parentElement + HTMLElementBinary: this.data.HTMLElementCardFields && isCardFieldsEligible && isCardFieldsAvailable + ? this.children.cardFields.data.HTMLElementButton.parentElement : this.data.HTMLElementSmartButton } }) diff --git a/_dev/js/front/src/service/paypal.service.js b/_dev/js/front/src/service/paypal.service.js index 8ec33bc13..03407f275 100644 --- a/_dev/js/front/src/service/paypal.service.js +++ b/_dev/js/front/src/service/paypal.service.js @@ -45,6 +45,17 @@ * @property {string} text.align */ +/** + * @typedef PayPayCardFieldsOptions + * @type {*} + * + * @property {function} createOrder + * @property {function} onApprove + * @property {function} onError + * @property {function} inputEvents + * @property {object} style + */ + /** * @typedef PaypalPayLaterOfferEvents * @type {*} @@ -244,7 +255,7 @@ export class PayPalService extends BaseClass { // Change card bg depending on card type if (event.cards.length === 1) { - document.querySelector('.defautl-credit-card').style.display = + document.querySelector('.default-credit-card').style.display = 'none'; const cardImage = document.getElementById('card-image'); @@ -262,7 +273,7 @@ export class PayPalService extends BaseClass { }); } } else { - document.querySelector('.defautl-credit-card').style.display = + document.querySelector('.default-credit-card').style.display = 'block'; const cardImage = document.getElementById('card-image'); cardImage.className = ''; @@ -279,6 +290,64 @@ export class PayPalService extends BaseClass { }); } + /** + * @param {*} fieldSelectors + * @param {string} fieldSelectors.name + * @param {string} fieldSelectors.number + * @param {string} fieldSelectors.cvv + * @param {string} fieldSelectors.expiry + * + * @param {PayPayCardFieldsOptions} options + * + * @returns {PayPalSdk.CardFields} + */ + async getCardFields(fieldSelectors, options) { + const cardFields = this.sdk.CardFields(options); + + + const nameField = cardFields.NameField({ + placeholder: this.$('paypal.hosted-fields.placeholder.card-name') + }); + const numberField = cardFields.NumberField({ + placeholder: this.$('paypal.hosted-fields.placeholder.card-number') + }); + const expiryField = cardFields.ExpiryField({ + placeholder: this.$('paypal.hosted-fields.placeholder.expiration-date') + }); + const cvvField = cardFields.CVVField({ + placeholder: this.$( + 'paypal.hosted-fields.placeholder.cvv' + ) + }); + + try { + await numberField.render(fieldSelectors.number); + await expiryField.render(fieldSelectors.expiry); + await cvvField.render(fieldSelectors.cvv); + await nameField.render(fieldSelectors.name); + } catch (e) { + return console.error("Failed to render CardFields", e); + } + + const nameLabel = document.querySelector( + `label[for="${fieldSelectors.name.id}"]` + ); + const numberLabel = document.querySelector( + `label[for="${fieldSelectors.number.id}"]` + ); + const cvvLabel = document.querySelector(`label[for="${fieldSelectors.cvv.id}"]`); + const expirationDateLabel = document.querySelector( + `label[for="${fieldSelectors.expiry.id}"]` + ); + + nameLabel.innerHTML = this.$('paypal.hosted-fields.label.card-name'); + numberLabel.innerHTML = this.$('paypal.hosted-fields.label.card-number'); + cvvLabel.innerHTML = this.$('paypal.hosted-fields.label.cvv'); + expirationDateLabel.innerHTML = this.$('paypal.hosted-fields.label.expiration-date'); + + return cardFields; + } + getEligibleFundingSources(cache = false) { if (!this.eligibleFundingSources || cache) { const paypalFundingSources = this.sdk.getFundingSources(); @@ -293,8 +362,9 @@ export class PayPalService extends BaseClass { mark: this.sdk.Marks({ fundingSource }) })) .filter((fundingSource) => { - if (fundingSource.name === 'card' && this.configPrestaShop.hostedFieldsEnabled && !this.isHostedFieldsEligible()) { - console.error('Hosted Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)'); + if (fundingSource.name === 'card' && this.configPrestaShop.hostedFieldsEnabled && !this.isCardFieldsEligible()) { + console.log(this.configPrestaShop.hostedFieldsEnabled, this.isCardFieldsEligible()); + console.error('Card Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)'); } console.log(fundingSource.name, fundingSource.mark.isEligible()); @@ -313,6 +383,10 @@ export class PayPalService extends BaseClass { return this.sdk.HostedFields && this.sdk.HostedFields.isEligible(); } + isCardFieldsEligible() { + return this.sdk.CardFields && this.sdk.CardFields().isEligible(); + } + /** * @param {string} placement * @param {string} amount diff --git a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js index 2c167249e..bfdf593f9 100644 --- a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js +++ b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js @@ -42,5 +42,18 @@ export const DefaultSelectors1_6 = { PAY_LATER_OFFER_MESSAGE_CONTAINER_PRODUCT: '.content_prices', PAY_LATER_OFFER_MESSAGE_CONTAINER_CART_SUMMARY: '#total_price_container', - PAY_LATER_BANNER_CONTAINER: '.header-container' + PAY_LATER_BANNER_CONTAINER: '.header-container', + + CARD_FIELDS: { + FORM: '#ps_checkout-card-fields-form', + NAME: '#ps_checkout-card-fields-name', + NUMBER: '#ps_checkout-card-fields-number', + EXPIRY: '#ps_checkout-card-fields-expiry', + CVV: '#ps_checkout-card-fields-cvv', + NAME_ERROR: '#ps_checkout-card-fields-name-error', + NUMBER_ERROR: '#ps_checkout-card-fields-number-error', + VENDOR_ERROR: '#ps_checkout-card-fields-vendor-error', + EXPIRY_ERROR: '#ps_checkout-card-fields-expiry-error', + CVV_ERROR: '#ps_checkout-card-fields-cvv-error', + } }; diff --git a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7.js b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7.js index d9bbeebbd..076644e02 100644 --- a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7.js +++ b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7.js @@ -43,5 +43,18 @@ export const DefaultSelectors1_7 = { PAY_LATER_OFFER_MESSAGE_CONTAINER_PRODUCT: '.product-prices', PAY_LATER_OFFER_MESSAGE_CONTAINER_CART_SUMMARY: '.cart-summary-totals', - PAY_LATER_BANNER_CONTAINER: '#notifications .container' + PAY_LATER_BANNER_CONTAINER: '#notifications .container', + + CARD_FIELDS: { + FORM: '#ps_checkout-card-fields-form', + NAME: '#ps_checkout-card-fields-name', + NUMBER: '#ps_checkout-card-fields-number', + EXPIRY: '#ps_checkout-card-fields-expiry', + CVV: '#ps_checkout-card-fields-cvv', + NAME_ERROR: '#ps_checkout-card-fields-name-error', + NUMBER_ERROR: '#ps_checkout-card-fields-number-error', + VENDOR_ERROR: '#ps_checkout-card-fields-vendor-error', + EXPIRY_ERROR: '#ps_checkout-card-fields-expiry-error', + CVV_ERROR: '#ps_checkout-card-fields-cvv-error', + } }; diff --git a/_dev/js/front/src/service/query-selector.service/index.js b/_dev/js/front/src/service/query-selector.service/index.js index 412e2616f..dbac2a5d4 100644 --- a/_dev/js/front/src/service/query-selector.service/index.js +++ b/_dev/js/front/src/service/query-selector.service/index.js @@ -98,4 +98,45 @@ export class QuerySelectorService extends BaseClass { getPayLaterOfferBannerContainerSelector(placement) { return this.instance.getPayLaterOfferBannerContainerSelector(placement); } + + getCardFieldsFormContainer() { + return this.instance.getCardFieldsFormContainer(); + } + + getCardFieldsNameInputContainer() { + return this.instance.getCardFieldsNameInputContainer(); + } + + getCardFieldsNameError() { + return this.instance.getCardFieldsNameError(); + + } + + getCardFieldsNumberInputContainer() { + return this.instance.getCardFieldsNumberInputContainer(); + } + + getCardFieldsNumberError() { + return this.instance.getCardFieldsNumberError(); + } + + getCardFieldsVendorError() { + return this.instance.getCardFieldsVendorError(); + } + + getCardFieldsExpiryInputContainer() { + return this.instance.getCardFieldsExpiryInputContainer(); + } + + getCardFieldsExpiryError() { + return this.instance.getCardFieldsExpiryError(); + } + + getCardFieldsCvvInputContainer() { + return this.instance.getCardFieldsCvvInputContainer(); + } + + getCardFieldsCvvError() { + return this.instance.getCardFieldsCvvError(); + } } diff --git a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_6.service.js b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_6.service.js index 7e5dd78a6..67c9fd238 100644 --- a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_6.service.js +++ b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_6.service.js @@ -82,6 +82,66 @@ export class QuerySelectorPs1_6Service { ); } + static getCardFieldsFormContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.FORM + ); + } + + static getCardFieldsNameInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NAME + ); + } + + static getCardFieldsNameError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NAME_ERROR + ); + } + + static getCardFieldsNumberInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NUMBER + ); + } + + static getCardFieldsNumberError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NUMBER_ERROR + ); + } + + static getCardFieldsVendorError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.VENDOR_ERROR + ); + } + + static getCardFieldsExpiryInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.EXPIRY + ); + } + + static getCardFieldsExpiryError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.EXPIRY_ERROR + ); + } + + static getCardFieldsCvvInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.CVV + ); + } + + static getCardFieldsCvvError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.CVV_ERROR + ); + } + static getPayLaterOfferMessageContainerSelector(placement) { switch (placement) { case 'product': diff --git a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js index 110be83f1..1728133bc 100644 --- a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js +++ b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js @@ -82,6 +82,66 @@ export class QuerySelectorPs1_7Service { ); } + static getCardFieldsFormContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.FORM + ); + } + + static getCardFieldsNameInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NAME + ); + } + + static getCardFieldsNameError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NAME_ERROR + ); + } + + static getCardFieldsNumberInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NUMBER + ); + } + + static getCardFieldsNumberError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.NUMBER_ERROR + ); + } + + static getCardFieldsVendorError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.VENDOR_ERROR + ); + } + + static getCardFieldsExpiryInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.EXPIRY + ); + } + + static getCardFieldsExpiryError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.EXPIRY_ERROR + ); + } + + static getCardFieldsCvvInputContainer() { + return this.querySelector( + SELECTORS.CARD_FIELDS.CVV + ); + } + + static getCardFieldsCvvError() { + return this.querySelector( + SELECTORS.CARD_FIELDS.CVV_ERROR + ); + } + static getPayLaterOfferMessageContainerSelector(placement) { switch (placement) { case 'product': diff --git a/ps_checkout.php b/ps_checkout.php index 25a005953..ac87a0cb4 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -1125,6 +1125,8 @@ public function hookActionFrontControllerSetMedia() 'checkout.order.amount.total' => $this->l('The total amount of your order comes to'), 'checkout.order.included.tax' => $this->l('(tax incl.)'), 'checkout.order.confirm.label' => $this->l('Please confirm your order by clicking "I confirm my order".'), + 'paypal.hosted-fields.label.card-name' => $this->l('Card holder name'), + 'paypal.hosted-fields.placeholder.card-name' => $this->l('Card holder name'), 'paypal.hosted-fields.label.card-number' => $this->l('Card number'), 'paypal.hosted-fields.placeholder.card-number' => $this->l('Card number'), 'paypal.hosted-fields.label.expiration-date' => $this->l('Expiry date'), diff --git a/views/css/payments.css b/views/css/payments.css index 45f1a259f..d03182e6f 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -513,3 +513,20 @@ label[for="ps_checkout-hosted-fields-card-cvv"] { .cart-detailed-totals + #ps-checkout-pp-message-container{ padding: 0 1rem 1rem; } + +#ps_checkout-card-fields-form.loading > *:not(#ps_checkout-card-fields-form-loader), +#ps_checkout-card-fields-form:not(.loading) > #ps_checkout-card-fields-form-loader { + display: none; +} + +#ps_checkout-card-fields-name-error.hidden, +#ps_checkout-card-fields-number-error.hidden, +#ps_checkout-card-fields-vendor-error.hidden, +#ps_checkout-card-fields-expiry-error.hidden, +#ps_checkout-card-fields-cvv-error.hidden { + display: none; +} + +#ps_checkout-card-fields-form-loader { + text-align: center; +} diff --git a/views/css/payments16.css b/views/css/payments16.css index 3ebf26025..598440d24 100644 --- a/views/css/payments16.css +++ b/views/css/payments16.css @@ -473,3 +473,20 @@ #ps_checkout-canceled img, #ps_checkout-error img{ margin-right: 10px; } + +#ps_checkout-card-fields-form.loading > *:not(#ps_checkout-card-fields-form-loader), +#ps_checkout-card-fields-form:not(.loading) > #ps_checkout-card-fields-form-loader { + display: none; +} + +#ps_checkout-card-fields-name-error.hidden, +#ps_checkout-card-fields-number-error.hidden, +#ps_checkout-card-fields-vendor-error.hidden, +#ps_checkout-card-fields-expiry-error.hidden, +#ps_checkout-card-fields-cvv-error.hidden { + display: none; +} + +#ps_checkout-card-fields-form-loader { + text-align: center; +} diff --git a/views/templates/hook/displayPayment.tpl b/views/templates/hook/displayPayment.tpl index f609fb529..69de27e9c 100644 --- a/views/templates/hook/displayPayment.tpl +++ b/views/templates/hook/displayPayment.tpl @@ -49,22 +49,29 @@
{if $fundingSource == 'card' && $isHostedFieldsAvailable} -
-
- -
-
- -
-
+ +
+ +
+
+ +
+ +
+
+ +
+ +
-
- -
+
+ +
+
-
- +
+
i
-
+
+
From c06d93b61995c1f072ca54b72b2e65a7ac66e84e Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 23 Jan 2024 16:11:40 +0200 Subject: [PATCH 2/3] Changed input padding --- _dev/js/front/src/components/common/card-fields.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index bc9603852..3ddf4152a 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -176,7 +176,7 @@ export class CardFieldsComponent extends BaseComponent { 'font-size': '17px', 'font-family': 'helvetica, tahoma, calibri, sans-serif', color: '#3a3a3a', - padding: '8px 10px' + padding: '8px 12px' }, ':focus': { color: 'black' From 83afbf6cf24f8c245c1e89ece183d82ae69547a3 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Thu, 1 Feb 2024 10:22:34 +0200 Subject: [PATCH 3/3] changed isCardFields API call property back to isHostedFields --- _dev/js/front/src/components/common/card-fields.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 3ddf4152a..51f76f0a3 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -206,7 +206,7 @@ export class CardFieldsComponent extends BaseComponent { .postCreateOrder({ ...data, fundingSource: this.data.name, - isCardFields: true, + isHostedFields: true, // vault: storeCardInVault }) .then(data => {