From 51517f1196028e6a086d50aa2150dfeca5e85004 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Nov 2021 12:56:53 +0200 Subject: [PATCH 01/11] Extract selector to a variable --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index b027f2f7e..39bbbb2d6 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -7,6 +7,8 @@ class CheckoutBootstap { this.renderer = renderer; this.messages = messages; this.spinner = spinner; + + this.standardOrderButtonSelector = '#place_order'; } init() { @@ -72,10 +74,10 @@ class CheckoutBootstap { this.renderer.hideButtons(this.gateway.button.wrapper); this.renderer.hideButtons(this.gateway.messages.wrapper); this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); - jQuery('#place_order').show(); + jQuery(this.standardOrderButtonSelector).show(); } else { - jQuery('#place_order').hide(); + jQuery(this.standardOrderButtonSelector).hide(); if (currentPaymentMethod === 'ppcp-gateway') { this.renderer.showButtons(this.gateway.button.wrapper); this.renderer.showButtons(this.gateway.messages.wrapper); @@ -101,10 +103,10 @@ class CheckoutBootstap { this.renderer.hideButtons(this.gateway.button.wrapper) this.renderer.hideButtons(this.gateway.messages.wrapper) this.renderer.hideButtons(this.gateway.hosted_fields.wrapper) - jQuery('#place_order').show() + jQuery(this.standardOrderButtonSelector).show() this.disableCreditCardFields() } else { - jQuery('#place_order').hide() + jQuery(this.standardOrderButtonSelector).hide() this.renderer.hideButtons(this.gateway.button.wrapper) this.renderer.hideButtons(this.gateway.messages.wrapper) this.renderer.showButtons(this.gateway.hosted_fields.wrapper) From 9624c85a6ce27aeb314cac536d654a4e63b2f418 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Nov 2021 16:27:00 +0200 Subject: [PATCH 02/11] Extract currentPaymentMethod --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 39bbbb2d6..f34436c93 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -67,8 +67,7 @@ class CheckoutBootstap { switchBetweenPayPalandOrderButton() { jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); - const currentPaymentMethod = jQuery( - 'input[name="payment_method"]:checked').val(); + const currentPaymentMethod = this.currentPaymentMethod(); if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') { this.renderer.hideButtons(this.gateway.button.wrapper); @@ -93,8 +92,7 @@ class CheckoutBootstap { } displayPlaceOrderButtonForSavedCreditCards() { - const currentPaymentMethod = jQuery( - 'input[name="payment_method"]:checked').val(); + const currentPaymentMethod = this.currentPaymentMethod(); if (currentPaymentMethod !== 'ppcp-credit-card-gateway') { return; } @@ -139,6 +137,10 @@ class CheckoutBootstap { jQuery('#ppcp-credit-card-vault').attr("disabled", false) this.renderer.enableCreditCardFields() } + + currentPaymentMethod() { + return jQuery('input[name="payment_method"]:checked').val(); + } } export default CheckoutBootstap From 2e59248bf96a8697b2651da2d554ae07fef4d6cc Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 10 Nov 2021 17:10:07 +0200 Subject: [PATCH 03/11] Extract saved card selection check --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index f34436c93..87cf637de 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -97,7 +97,7 @@ class CheckoutBootstap { return; } - if (jQuery('#saved-credit-card').length && jQuery('#saved-credit-card').val() !== '') { + if (this.isSavedCardSelected()) { this.renderer.hideButtons(this.gateway.button.wrapper) this.renderer.hideButtons(this.gateway.messages.wrapper) this.renderer.hideButtons(this.gateway.hosted_fields.wrapper) @@ -141,6 +141,11 @@ class CheckoutBootstap { currentPaymentMethod() { return jQuery('input[name="payment_method"]:checked').val(); } + + isSavedCardSelected() { + const savedCardList = jQuery('#saved-credit-card'); + return savedCardList.length && savedCardList.val() !== ''; + } } export default CheckoutBootstap From c2b77b3cff2ee0296b5e5d1133cb88d28d2bfa72 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 10:57:17 +0200 Subject: [PATCH 04/11] Unselect saved card only on page load If we do it on every form update/method switch, then e.g. if the user first selected a card, but then entered/edited the address, they may lose the selection (and maybe it could cause issues in other cases, if something else triggers update_checkout) --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 87cf637de..1a7ce920e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -12,9 +12,14 @@ class CheckoutBootstap { } init() { - this.render(); + // Unselect saved card. + // WC saves form values, so with our current UI it would be a bit weird + // if the user paid with saved, then after some time tries to pay again, + // but wants to enter a new card, and to do that they have to choose “Select payment” in the list. + jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); + jQuery(document.body).on('updated_checkout', () => { this.render() }); @@ -65,8 +70,6 @@ class CheckoutBootstap { } switchBetweenPayPalandOrderButton() { - jQuery('#saved-credit-card').val(jQuery('#saved-credit-card option:first').val()); - const currentPaymentMethod = this.currentPaymentMethod(); if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') { From 9ad48d5f1300b150159f90590fe3d9ee97c2909f Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 15:20:21 +0200 Subject: [PATCH 05/11] Optimize checkout elements hiding Remove duplicated calls like hidden -> show() -> hide() during some updates and make code more clear --- .../ContextBootstrap/CheckoutBootstap.js | 71 ++++++------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 1a7ce920e..f3a06c7ab 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -24,21 +24,17 @@ class CheckoutBootstap { this.render() }); - jQuery(document.body). - on('updated_checkout payment_method_selected', () => { - this.switchBetweenPayPalandOrderButton() - this.displayPlaceOrderButtonForSavedCreditCards() - - }) + jQuery(document.body).on('updated_checkout payment_method_selected', () => { + this.updateUi(); + }); jQuery(document).on('hosted_fields_loaded', () => { jQuery('#saved-credit-card').on('change', () => { - this.displayPlaceOrderButtonForSavedCreditCards() + this.updateUi(); }) }); - this.switchBetweenPayPalandOrderButton() - this.displayPlaceOrderButtonForSavedCreditCards() + this.updateUi(); } shouldRender() { @@ -69,49 +65,28 @@ class CheckoutBootstap { ); } - switchBetweenPayPalandOrderButton() { + updateUi() { const currentPaymentMethod = this.currentPaymentMethod(); + const isPaypal = currentPaymentMethod === 'ppcp-gateway'; + const isCard = currentPaymentMethod === 'ppcp-credit-card-gateway'; + const isSavedCard = isCard && this.isSavedCardSelected(); + const isNotOurGateway = !isPaypal && !isCard; - if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') { - this.renderer.hideButtons(this.gateway.button.wrapper); - this.renderer.hideButtons(this.gateway.messages.wrapper); - this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); - jQuery(this.standardOrderButtonSelector).show(); + jQuery(this.standardOrderButtonSelector).toggle(isNotOurGateway || isSavedCard); + jQuery(this.gateway.button.wrapper).toggle(isPaypal); + jQuery(this.gateway.messages.wrapper).toggle(isPaypal); + jQuery(this.gateway.hosted_fields.wrapper).toggle(isCard && !isSavedCard); + + if (isPaypal) { + this.messages.render(); } - else { - jQuery(this.standardOrderButtonSelector).hide(); - if (currentPaymentMethod === 'ppcp-gateway') { - this.renderer.showButtons(this.gateway.button.wrapper); - this.renderer.showButtons(this.gateway.messages.wrapper); - this.messages.render() - this.renderer.hideButtons(this.gateway.hosted_fields.wrapper) + + if (isCard) { + if (isSavedCard) { + this.disableCreditCardFields(); + } else { + this.enableCreditCardFields(); } - if (currentPaymentMethod === 'ppcp-credit-card-gateway') { - this.renderer.hideButtons(this.gateway.button.wrapper) - this.renderer.hideButtons(this.gateway.messages.wrapper) - this.renderer.showButtons(this.gateway.hosted_fields.wrapper) - } - } - } - - displayPlaceOrderButtonForSavedCreditCards() { - const currentPaymentMethod = this.currentPaymentMethod(); - if (currentPaymentMethod !== 'ppcp-credit-card-gateway') { - return; - } - - if (this.isSavedCardSelected()) { - this.renderer.hideButtons(this.gateway.button.wrapper) - this.renderer.hideButtons(this.gateway.messages.wrapper) - this.renderer.hideButtons(this.gateway.hosted_fields.wrapper) - jQuery(this.standardOrderButtonSelector).show() - this.disableCreditCardFields() - } else { - jQuery(this.standardOrderButtonSelector).hide() - this.renderer.hideButtons(this.gateway.button.wrapper) - this.renderer.hideButtons(this.gateway.messages.wrapper) - this.renderer.showButtons(this.gateway.hosted_fields.wrapper) - this.enableCreditCardFields() } } From b9ae2483adf7487d95bcf147fd321db1e6d2f986 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 17:57:23 +0200 Subject: [PATCH 06/11] Add CSS class to dcc order button --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e83510873..8a31c9788 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -560,7 +560,7 @@ class SmartButton implements SmartButtonInterface { printf( '
', esc_attr( $id ), esc_html( $label ) From c97dc9530bba0d7717b4b033e8abb5c335cabc34 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 17:58:08 +0200 Subject: [PATCH 07/11] Add float: right to dcc order button like in WC styles --- modules/ppcp-button/resources/css/hosted-fields.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-button/resources/css/hosted-fields.scss b/modules/ppcp-button/resources/css/hosted-fields.scss index ba6328001..2305d313b 100644 --- a/modules/ppcp-button/resources/css/hosted-fields.scss +++ b/modules/ppcp-button/resources/css/hosted-fields.scss @@ -11,3 +11,7 @@ .ppcp-credit-card-gateway-form-field-disabled { opacity: .5 !important; } + +.ppcp-dcc-order-button { + float: right; +} From 3dc9b48e1b4cec9d2a43b52411583f4d48d0ee29 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 18:00:20 +0200 Subject: [PATCH 08/11] Hide button with !important to override !important from some themes --- .../ContextBootstrap/CheckoutBootstap.js | 9 ++-- .../resources/js/modules/Helper/Hiding.js | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/Hiding.js diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index f3a06c7ab..d5df1460d 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,5 +1,6 @@ import ErrorHandler from '../ErrorHandler'; import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; +import { setVisible } from '../Helper/Hiding'; class CheckoutBootstap { constructor(gateway, renderer, messages, spinner) { @@ -72,10 +73,10 @@ class CheckoutBootstap { const isSavedCard = isCard && this.isSavedCardSelected(); const isNotOurGateway = !isPaypal && !isCard; - jQuery(this.standardOrderButtonSelector).toggle(isNotOurGateway || isSavedCard); - jQuery(this.gateway.button.wrapper).toggle(isPaypal); - jQuery(this.gateway.messages.wrapper).toggle(isPaypal); - jQuery(this.gateway.hosted_fields.wrapper).toggle(isCard && !isSavedCard); + setVisible(this.standardOrderButtonSelector, isNotOurGateway || isSavedCard, true); + setVisible(this.gateway.button.wrapper, isPaypal); + setVisible(this.gateway.messages.wrapper, isPaypal); + setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); if (isPaypal) { this.messages.render(); diff --git a/modules/ppcp-button/resources/js/modules/Helper/Hiding.js b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js new file mode 100644 index 000000000..8a76164f7 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js @@ -0,0 +1,44 @@ +const getElement = (selectorOrElement) => { + if (typeof selectorOrElement === 'string') { + return document.querySelector(selectorOrElement); + } + return selectorOrElement; +} + +export const isVisible = (element) => { + return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length); +} + +export const setVisible = (selectorOrElement, show, important = false) => { + const element = getElement(selectorOrElement); + if (!element) { + return; + } + + const currentValue = element.style.getPropertyValue('display'); + + if (!show) { + if (currentValue === 'none') { + return; + } + + element.style.setProperty('display', 'none', important ? 'important' : ''); + } else { + if (currentValue === 'none') { + element.style.removeProperty('display'); + } + + // still not visible (if something else added display: none in CSS) + if (!isVisible(element)) { + element.style.setProperty('display', 'block'); + } + } +}; + +export const hide = (selectorOrElement, important = false) => { + setVisible(selectorOrElement, false, important); +}; + +export const show = (selectorOrElement) => { + setVisible(selectorOrElement, true); +}; From 9c7859bf35de6cbd425b0fd2b511710cac86588d Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 11 Nov 2021 18:03:50 +0200 Subject: [PATCH 09/11] Use MutationObserver to detect order button visibility changes Such as for the old PayPal plugin, which shows it on click + with some animation, or Conditional WooCommerce Checkout Field, which shows the button after loading some data --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index d5df1460d..9519f9073 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -10,6 +10,10 @@ class CheckoutBootstap { this.spinner = spinner; this.standardOrderButtonSelector = '#place_order'; + + this.buttonChangeObserver = new MutationObserver((el) => { + this.updateUi(); + }); } init() { @@ -64,6 +68,11 @@ class CheckoutBootstap { this.gateway.hosted_fields.wrapper, actionHandler.configuration(), ); + + this.buttonChangeObserver.observe( + document.querySelector(this.standardOrderButtonSelector), + {attributes: true} + ); } updateUi() { From ebe1e849073d661ab28342c341ec2dee83f591e5 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 12 Nov 2021 18:27:47 +0200 Subject: [PATCH 10/11] Fix and refactor PayNow checkout UI too Inherit PayNowBootstrap from CheckoutBootstap, they are almost the same --- .../ContextBootstrap/PayNowBootstrap.js | 79 ++----------------- 1 file changed, 5 insertions(+), 74 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js index 7aa37ba03..cd52d4b9e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js @@ -1,86 +1,17 @@ -import ErrorHandler from '../ErrorHandler'; -import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; +import CheckoutBootstap from './CheckoutBootstap' -class PayNowBootstrap { +class PayNowBootstrap extends CheckoutBootstap { constructor(gateway, renderer, messages, spinner) { - this.gateway = gateway; - this.renderer = renderer; - this.messages = messages; - this.spinner = spinner; + super(gateway, renderer, messages, spinner) } - init() { - - this.render(); - - jQuery(document.body).on('updated_checkout', () => { - this.render(); - }); - - jQuery(document.body). - on('updated_checkout payment_method_selected', () => { - this.switchBetweenPayPalandOrderButton(); - }); - this.switchBetweenPayPalandOrderButton(); - } - - shouldRender() { - if (document.querySelector(this.gateway.button.cancel_wrapper)) { - return false; - } - - return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null; - } - - render() { - if (!this.shouldRender()) { - return; - } - if (document.querySelector(this.gateway.hosted_fields.wrapper + '>div')) { - document.querySelector(this.gateway.hosted_fields.wrapper + '>div').setAttribute('style', ''); - } - const actionHandler = new CheckoutActionHandler( - PayPalCommerceGateway, - new ErrorHandler(this.gateway.labels.error.generic), - this.spinner - ); - - this.renderer.render( - this.gateway.button.wrapper, - this.gateway.hosted_fields.wrapper, - actionHandler.configuration(), - ); - } - - switchBetweenPayPalandOrderButton() { + updateUi() { const urlParams = new URLSearchParams(window.location.search) if (urlParams.has('change_payment_method')) { return } - const currentPaymentMethod = jQuery( - 'input[name="payment_method"]:checked').val(); - - if (currentPaymentMethod !== 'ppcp-gateway' && currentPaymentMethod !== 'ppcp-credit-card-gateway') { - this.renderer.hideButtons(this.gateway.button.wrapper); - this.renderer.hideButtons(this.gateway.messages.wrapper); - this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); - jQuery('#place_order').show(); - } - else { - jQuery('#place_order').hide(); - if (currentPaymentMethod === 'ppcp-gateway') { - this.renderer.showButtons(this.gateway.button.wrapper); - this.renderer.showButtons(this.gateway.messages.wrapper); - this.messages.render(); - this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); - } - if (currentPaymentMethod === 'ppcp-credit-card-gateway') { - this.renderer.hideButtons(this.gateway.button.wrapper); - this.renderer.hideButtons(this.gateway.messages.wrapper); - this.renderer.showButtons(this.gateway.hosted_fields.wrapper); - } - } + super.updateUi(); } } From 1f8b449e38722fafc180ab1bf2076a8e89a02d70 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 12 Nov 2021 18:29:30 +0200 Subject: [PATCH 11/11] Fix change payment check to not conflict with Pay Now --- modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php index de1e0a70c..b228a281a 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php +++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php @@ -58,8 +58,8 @@ trait ProcessPaymentTrait { * If customer has chosen a saved credit card payment. */ $saved_credit_card = filter_input( INPUT_POST, 'saved_credit_card', FILTER_SANITIZE_STRING ); - $pay_for_order = filter_input( INPUT_GET, 'pay_for_order', FILTER_SANITIZE_STRING ); - if ( $saved_credit_card && ! isset( $pay_for_order ) ) { + $change_payment = filter_input( INPUT_GET, 'woocommerce_change_payment', FILTER_SANITIZE_STRING ); + if ( $saved_credit_card && ! isset( $change_payment ) ) { $user_id = (int) $wc_order->get_customer_id(); $customer = new \WC_Customer( $user_id );