From 25c5ef2e9e0e778e1469d193429483e2b1e8f5aa Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Wed, 26 Jun 2024 17:56:48 +0200
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20New=20helper=20to=20monitor=20multi?=
=?UTF-8?q?step=20checkout=20forms?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The new script adds an interval to monitor the visibility of the “woocommerce-checkout-payment” element to initialize payment buttons with a delay, if needed.
---
modules/ppcp-button/resources/js/button.js | 3 +
.../modules/Helper/MultistepCheckoutHelper.js | 121 ++++++++++++++++++
2 files changed, 124 insertions(+)
create mode 100644 modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js
diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js
index d0e1a8543..e680115ba 100644
--- a/modules/ppcp-button/resources/js/button.js
+++ b/modules/ppcp-button/resources/js/button.js
@@ -17,6 +17,7 @@ import {
import {setVisibleByClass} from "./modules/Helper/Hiding";
import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
+import MultistepCheckoutHelper from "./modules/Helper/MultistepCheckoutHelper";
import FormSaver from './modules/Helper/FormSaver';
import FormValidator from "./modules/Helper/FormValidator";
import {loadPaypalScript} from "./modules/Helper/ScriptLoading";
@@ -53,6 +54,8 @@ const bootstrap = () => {
const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler);
+ new MultistepCheckoutHelper(checkoutFormSelector);
+
jQuery('form.woocommerce-checkout input').on('keydown', e => {
if (e.key === 'Enter' && [
PaymentMethods.PAYPAL,
diff --git a/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js b/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js
new file mode 100644
index 000000000..c4eec6143
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/MultistepCheckoutHelper.js
@@ -0,0 +1,121 @@
+/**
+ * The MultistepCheckoutHelper class ensures the initialization of payment buttons
+ * on websites using a multistep checkout plugin. These plugins usually hide the
+ * payment button on page load up and reveal it later using JS. During the
+ * invisibility period of wrappers, some payment buttons fail to initialize,
+ * so we wait for the payment element to be visible.
+ *
+ * @property {HTMLElement} form - Checkout form element.
+ * @property {HTMLElement} triggerElement - Element, which visibility we need to detect.
+ * @property {boolean} isVisible - Whether the triggerElement is visible.
+ */
+class MultistepCheckoutHelper {
+
+ /**
+ * Configuration that defines the HTML selector for the component we are waiting to be visible.
+ * @type {string}
+ */
+ #triggerElementSelector = '.woocommerce-checkout-payment';
+
+ /**
+ * Interval (in milliseconds) in which the visibility of the trigger element is checked.
+ * @type {number}
+ */
+ #intervalTime = 150;
+
+ /**
+ * The interval ID returned by the setInterval() method.
+ * @type {number|false}
+ */
+ #intervalId;
+
+ /**
+ * Selector passed to the constructor that identifies the checkout form
+ * @type {string}
+ */
+ #formSelector;
+
+ /**
+ * @param {string} formSelector - Selector of the checkout form
+ */
+ constructor(formSelector) {
+ this.#formSelector = formSelector;
+ this.#intervalId = false;
+
+ /*
+ Start the visibility checker after a brief delay. This allows eventual multistep plugins to
+ dynamically prepare the checkout page, so we can decide whether this helper is needed.
+ */
+ setTimeout(() => {
+ if (this.form && !this.isVisible) {
+ this.start();
+ }
+ }, 250);
+ }
+
+ /**
+ * The checkout form element.
+ * @returns {Element|null} - Form element or null.
+ */
+ get form() {
+ return document.querySelector(this.#formSelector);
+ }
+
+ /**
+ * The element which must be visible before payment buttons should be initialized.
+ * @returns {Element|null} - Trigger element or null.
+ */
+ get triggerElement() {
+ return this.form?.querySelector(this.#triggerElementSelector);
+ }
+
+ /**
+ * Checks the visibility of the payment button wrapper.
+ * @returns {boolean} - returns boolean value on the basis of visibility of element.
+ */
+ get isVisible() {
+ const box = this.triggerElement?.getBoundingClientRect();
+
+ return !!(box && box.width && box.height);
+ }
+
+ /**
+ * Starts the observation of the DOM, initiates monitoring the checkout form.
+ * To ensure multiple calls to start don't create multiple intervals, we first call stop.
+ */
+ start() {
+ this.stop();
+ this.#intervalId = setInterval(() => this.checkElement(), this.#intervalTime);
+ }
+
+ /**
+ * Stops the observation of the checkout form.
+ * Multiple calls to stop are safe as clearInterval doesn't throw if provided ID doesn't exist.
+ */
+ stop() {
+ if (this.#intervalId) {
+ clearInterval(this.#intervalId);
+ this.#intervalId = false;
+ }
+ }
+
+ /**
+ * Checks if the trigger element is visible.
+ * If visible, it initialises the payment buttons and stops the observation.
+ */
+ checkElement() {
+ if (this.isVisible) {
+ this.refreshButtons();
+ this.stop();
+ }
+ }
+
+ /**
+ * Initializes the payment buttons on the visibility of wrapper.
+ */
+ refreshButtons() {
+ document.dispatchEvent(new Event('ppcp_refresh_payment_buttons'));
+ }
+}
+
+export default MultistepCheckoutHelper;