From 7050c8231ea4d82e3e976ab2d987edd2005f8f78 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 Mar 2022 09:16:37 +0200 Subject: [PATCH 1/7] Refactor selected payment gateway checking --- .../ContextBootstrap/CheckoutBootstap.js | 24 ++++++++----------- .../js/modules/Helper/CheckoutMethodState.js | 20 ++++++++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 9519f9073..5ed0d60ff 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,6 +1,11 @@ import ErrorHandler from '../ErrorHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import { setVisible } from '../Helper/Hiding'; +import { + getCurrentPaymentMethod, + isSavedCardSelected, ORDER_BUTTON_SELECTOR, + PaymentMethods +} from "../Helper/CheckoutMethodState"; class CheckoutBootstap { constructor(gateway, renderer, messages, spinner) { @@ -9,7 +14,7 @@ class CheckoutBootstap { this.messages = messages; this.spinner = spinner; - this.standardOrderButtonSelector = '#place_order'; + this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; this.buttonChangeObserver = new MutationObserver((el) => { this.updateUi(); @@ -76,10 +81,10 @@ class CheckoutBootstap { } updateUi() { - const currentPaymentMethod = this.currentPaymentMethod(); - const isPaypal = currentPaymentMethod === 'ppcp-gateway'; - const isCard = currentPaymentMethod === 'ppcp-credit-card-gateway'; - const isSavedCard = isCard && this.isSavedCardSelected(); + const currentPaymentMethod = getCurrentPaymentMethod(); + const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; + const isCard = currentPaymentMethod === PaymentMethods.CARDS; + const isSavedCard = isCard && isSavedCardSelected(); const isNotOurGateway = !isPaypal && !isCard; setVisible(this.standardOrderButtonSelector, isNotOurGateway || isSavedCard, true); @@ -125,15 +130,6 @@ class CheckoutBootstap { jQuery('#ppcp-credit-card-vault').attr("disabled", false) this.renderer.enableCreditCardFields() } - - currentPaymentMethod() { - return jQuery('input[name="payment_method"]:checked').val(); - } - - isSavedCardSelected() { - const savedCardList = jQuery('#saved-credit-card'); - return savedCardList.length && savedCardList.val() !== ''; - } } export default CheckoutBootstap diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js new file mode 100644 index 000000000..196ad596b --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -0,0 +1,20 @@ +export const PaymentMethods = { + PAYPAL: 'ppcp-gateway', + CARDS: 'ppcp-credit-card-gateway', +}; + +export const ORDER_BUTTON_SELECTOR = '#place_order'; + +export const getCurrentPaymentMethod = () => { + const el = document.querySelector('input[name="payment_method"]:checked'); + if (!el) { + return null; + } + + return el.value; +}; + +export const isSavedCardSelected = () => { + const savedCardList = document.querySelector('#saved-credit-card'); + return savedCardList && savedCardList.value !== ''; +}; From f6944a620899b3fba91f801a7ba2fb4d951e62e8 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 15 Mar 2022 09:24:43 +0200 Subject: [PATCH 2/7] Hide "Place Order" button on page load when PayPal selected --- modules/ppcp-button/resources/js/button.js | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 402781bbf..dcaf81228 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -9,6 +9,12 @@ import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer"; import dataClientIdAttributeHandler from "./modules/DataClientIdAttributeHandler"; import MessageRenderer from "./modules/Renderer/MessageRenderer"; import Spinner from "./modules/Helper/Spinner"; +import { + getCurrentPaymentMethod, + ORDER_BUTTON_SELECTOR, + PaymentMethods +} from "./modules/Helper/CheckoutMethodState"; +import {setVisible} from "./modules/Helper/Hiding"; const bootstrap = () => { const errorHandler = new ErrorHandler(PayPalCommerceGateway.labels.error.generic); @@ -91,8 +97,30 @@ document.addEventListener( return; } + // Sometimes PayPal script takes long time to load, + // so we additionally hide the standard order button here to avoid failed orders. + // Normally it is hidden later after the script load. + const hideOrderButtonIfPpcpGateway = () => { + const currentPaymentMethod = getCurrentPaymentMethod(); + setVisible(ORDER_BUTTON_SELECTOR, currentPaymentMethod !== PaymentMethods.PAYPAL, true); + } + + let bootstrapped = false; + + hideOrderButtonIfPpcpGateway(); + + jQuery(document.body).on('updated_checkout payment_method_selected', () => { + if (bootstrapped) { + return; + } + + hideOrderButtonIfPpcpGateway(); + }); + const script = document.createElement('script'); script.addEventListener('load', (event) => { + bootstrapped = true; + bootstrap(); }); script.setAttribute('src', PayPalCommerceGateway.button.url); From 42345d4947de782bbd36110239c12754a744fd13 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 Mar 2022 09:48:00 +0200 Subject: [PATCH 3/7] Add buttons loading spinner --- modules/ppcp-button/resources/js/button.js | 22 ++++++++++++++++--- .../resources/js/modules/Helper/Spinner.js | 4 ++-- .../resources/js/modules/Renderer/Renderer.js | 4 +++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index dcaf81228..e3275da51 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -14,7 +14,9 @@ import { ORDER_BUTTON_SELECTOR, PaymentMethods } from "./modules/Helper/CheckoutMethodState"; -import {setVisible} from "./modules/Helper/Hiding"; +import {hide, setVisible} from "./modules/Helper/Hiding"; + +const buttonsSpinner = new Spinner('.place-order'); const bootstrap = () => { const errorHandler = new ErrorHandler(PayPalCommerceGateway.labels.error.generic); @@ -23,7 +25,10 @@ const bootstrap = () => { const onSmartButtonClick = data => { window.ppcpFundingSource = data.fundingSource; }; - const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick); + const onSmartButtonsInit = () => { + buttonsSpinner.unblock(); + }; + const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit); const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages); const context = PayPalCommerceGateway.context; if (context === 'mini-cart' || context === 'product') { @@ -102,7 +107,18 @@ document.addEventListener( // Normally it is hidden later after the script load. const hideOrderButtonIfPpcpGateway = () => { const currentPaymentMethod = getCurrentPaymentMethod(); - setVisible(ORDER_BUTTON_SELECTOR, currentPaymentMethod !== PaymentMethods.PAYPAL, true); + const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; + + setVisible(ORDER_BUTTON_SELECTOR, !isPaypal, true); + + if (PayPalCommerceGateway.context === 'checkout') { + if (isPaypal) { + // stopped after the first rendering of the buttons, in onInit + buttonsSpinner.block(); + } else { + buttonsSpinner.unblock(); + } + } } let bootstrapped = false; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js index 9c41eb08b..673e37a90 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js @@ -1,7 +1,7 @@ class Spinner { - constructor() { - this.target = 'form.woocommerce-checkout'; + constructor(target = 'form.woocommerce-checkout') { + this.target = target; } setTarget(target) { diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 43660c30c..cc811bb58 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -1,8 +1,9 @@ class Renderer { - constructor(creditCardRenderer, defaultConfig, onSmartButtonClick) { + constructor(creditCardRenderer, defaultConfig, onSmartButtonClick, onSmartButtonsInit) { this.defaultConfig = defaultConfig; this.creditCardRenderer = creditCardRenderer; this.onSmartButtonClick = onSmartButtonClick; + this.onSmartButtonsInit = onSmartButtonsInit; } render(wrapper, hostedFieldsWrapper, contextConfig) { @@ -21,6 +22,7 @@ class Renderer { style, ...contextConfig, onClick: this.onSmartButtonClick, + onInit: this.onSmartButtonsInit, }).render(wrapper); } From a1d735bc79730dcc37efa783c41fff84494c2a45 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 Mar 2022 10:31:18 +0200 Subject: [PATCH 4/7] Hide "Place order"/show spinner only on normal checkout --- modules/ppcp-button/resources/js/button.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index e3275da51..a6a1ce85d 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -106,18 +106,22 @@ document.addEventListener( // so we additionally hide the standard order button here to avoid failed orders. // Normally it is hidden later after the script load. const hideOrderButtonIfPpcpGateway = () => { + // only in checkout, otherwise it may break things (e.g. payment via product page), + // and also the loading spinner may look weird on other pages + if (PayPalCommerceGateway.context !== 'checkout') { + return; + } + const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; setVisible(ORDER_BUTTON_SELECTOR, !isPaypal, true); - if (PayPalCommerceGateway.context === 'checkout') { - if (isPaypal) { - // stopped after the first rendering of the buttons, in onInit - buttonsSpinner.block(); - } else { - buttonsSpinner.unblock(); - } + if (isPaypal) { + // stopped after the first rendering of the buttons, in onInit + buttonsSpinner.block(); + } else { + buttonsSpinner.unblock(); } } From d791873033a4f225cba2a4a2272464e63edd1508 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 Mar 2022 15:59:19 +0200 Subject: [PATCH 5/7] Move buttons loading spinner to the buttons container should be more reliable, and for Pay Now there is no .place-order --- modules/ppcp-button/resources/js/button.js | 2 +- modules/ppcp-button/src/Assets/SmartButton.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index a6a1ce85d..c0c2fd5b4 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -16,7 +16,7 @@ import { } from "./modules/Helper/CheckoutMethodState"; import {hide, setVisible} from "./modules/Helper/Hiding"; -const buttonsSpinner = new Spinner('.place-order'); +const buttonsSpinner = new Spinner('.ppc-button-wrapper'); const bootstrap = () => { const errorHandler = new ErrorHandler(PayPalCommerceGateway.labels.error.generic); diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 7c362d91e..6d812db7c 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -458,7 +458,9 @@ class SmartButton implements SmartButtonInterface { return; } - echo '
'; + // The wrapper is needed for the loading spinner, + // otherwise jQuery block() prevents buttons rendering. + echo '
'; } /** From 4fe5a63f42f4d73eb07c5613c660e96751d7553a Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 17 Mar 2022 16:00:55 +0200 Subject: [PATCH 6/7] Hide "Place order"/show spinner in Pay Now too --- modules/ppcp-button/resources/js/button.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index c0c2fd5b4..e5bb7fb96 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -106,9 +106,9 @@ document.addEventListener( // so we additionally hide the standard order button here to avoid failed orders. // Normally it is hidden later after the script load. const hideOrderButtonIfPpcpGateway = () => { - // only in checkout, otherwise it may break things (e.g. payment via product page), + // only in checkout and pay now page, otherwise it may break things (e.g. payment via product page), // and also the loading spinner may look weird on other pages - if (PayPalCommerceGateway.context !== 'checkout') { + if (!['checkout', 'pay-now'].includes(PayPalCommerceGateway.context)) { return; } From f0ff7de9fa2f54f1660453d8bdc94c7952740adb Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 18 Mar 2022 09:24:55 +0200 Subject: [PATCH 7/7] Do not hide button on change payment page --- modules/ppcp-button/resources/js/button.js | 6 +++++- .../js/modules/ContextBootstrap/PayNowBootstrap.js | 4 ++-- .../resources/js/modules/Helper/Subscriptions.js | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index e5bb7fb96..fddc82a57 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -15,6 +15,7 @@ import { PaymentMethods } from "./modules/Helper/CheckoutMethodState"; import {hide, setVisible} from "./modules/Helper/Hiding"; +import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; const buttonsSpinner = new Spinner('.ppc-button-wrapper'); @@ -108,7 +109,10 @@ document.addEventListener( const hideOrderButtonIfPpcpGateway = () => { // only in checkout and pay now page, otherwise it may break things (e.g. payment via product page), // and also the loading spinner may look weird on other pages - if (!['checkout', 'pay-now'].includes(PayPalCommerceGateway.context)) { + if ( + !['checkout', 'pay-now'].includes(PayPalCommerceGateway.context) + || isChangePaymentPage() + ) { return; } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js index cd52d4b9e..4b2583c98 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js @@ -1,4 +1,5 @@ import CheckoutBootstap from './CheckoutBootstap' +import {isChangePaymentPage} from "../Helper/Subscriptions"; class PayNowBootstrap extends CheckoutBootstap { constructor(gateway, renderer, messages, spinner) { @@ -6,8 +7,7 @@ class PayNowBootstrap extends CheckoutBootstap { } updateUi() { - const urlParams = new URLSearchParams(window.location.search) - if (urlParams.has('change_payment_method')) { + if (isChangePaymentPage()) { return } diff --git a/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js new file mode 100644 index 000000000..a205ac459 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js @@ -0,0 +1,4 @@ +export const isChangePaymentPage = () => { + const urlParams = new URLSearchParams(window.location.search) + return urlParams.has('change_payment_method'); +}