commerce_paypal-8.x-1.0-beta11/js/paypal-fastlane.js

js/paypal-fastlane.js
((Drupal, once) => {
  /**
   * The Drupal Commerce Fastlane Element Instances.
   *
   * Only one instance is supported.
   *
   * @type {Map}
   */
  Drupal.CommerceFastlaneInstances = new Map();

  class CommercePaypalFastlane {
    /**
     * Whether the component has been initialized.
     */
    initialized;

    /**
     * The settings.
     */
    settings;

    /**
     * The form.
     */
    form;

    /**
     * The active checkout pane id.
     */
    activeCheckoutPaneId;

    /**
     * The visited checkout pane ids.
     */
    visitedPaneIds;

    /**
     * The email.
     */
    email;

    /**
     * Whether the member is authenticated successfully.
     */
    memberAuthenticatedSuccessfully;

    /**
     * The overlay modal.
     */
    overlayModal;

    /**
     * Whether the form can be submitted.
     */
    allowSubmit;

    /**
     * The shipping address.
     */
    shippingAddress;

    /**
     * The profile.
     */
    profile;

    /**
     * The profile data.
     */
    profileData;

    /**
     * The payment token.
     */
    paymentToken;

    /**
     * The payment component.
     */
    paymentComponent;

    /**
     * The identity.
     */
    identity;

    /**
     * The Fastlane payment component.
     */
    FastlanePaymentComponent;

    /**
     * The Commerce PayPal Fastlane constructor.
     */
    constructor() {
      if (!this.initialized) {
        this.initialized = true;
        this.settings = null;
        this.form = null;
        this.activeCheckoutPaneId = null;
        this.visitedPaneIds = [];
        this.email = null;
        this.memberAuthenticatedSuccessfully = false;
        this.overlayModal = null;
        this.allowSubmit = false;
        this.shippingAddress = null;
        this.profile = null;
        this.profileData = null;
        this.paymentToken = null;
        this.paymentComponent = null;
        this.identity = null;
        this.FastlanePaymentComponent = null;
      }
    }

    /**
     * Initialize the fastlane component.
     *
     * @param {Object} settings
     *   The settings.
     */
    async initializeFastlane(settings) {
      // Ensure we initialize the script only once.
      const script = document.createElement('script');
      script.src = settings.src;
      script.type = 'text/javascript';
      script.setAttribute(
        'data-partner-attribution-id',
        'CommerceGuys_Cart_FL',
      );
      script.setAttribute('data-sdk-client-token', settings.clientToken);
      document.getElementsByTagName('head')[0].appendChild(script);

      async function waitForSdk() {
        while (typeof window.paypal === 'undefined') {
          /* eslint-disable-next-line no-await-in-loop */
          await new Promise((resolve) => {
            setTimeout(resolve, 100);
          });
        }
        return window.paypal;
      }
      await waitForSdk();
      await this.initializeCheckout(settings);
    }

    /**
     * Populates the shipping profile form.
     *
     * @param {*} shippingAddress
     *   The shipping address.
     * @return {Promise<boolean>}
     *   Whether the profile form was populated.
     */
    async populateShippingInformationProfileForm(shippingAddress) {
      if (
        shippingAddress?.address?.countryCode &&
        this.form?.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-address-line1"]',
        )
      ) {
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-given-name"]',
        ).value = shippingAddress?.name?.firstName ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-family-name"]',
        ).value = shippingAddress?.name?.lastName ?? '';

        this.form.querySelector(
          'select[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-country-code"]',
        ).value = shippingAddress?.address?.countryCode ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-postal-code"]',
        ).value = shippingAddress?.address?.postalCode ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-administrative-area"]',
        ).value = shippingAddress?.address?.adminArea1 ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-locality"]',
        ).value = shippingAddress?.address?.adminArea2 ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-address-line1"]',
        ).value = shippingAddress?.address?.addressLine1 ?? '';
        this.form.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipping-profile-address-0-address-address-line2"]',
        ).value = shippingAddress?.address?.addressLine2 ?? '';
        return true;
      }
      return false;
    }

    /**
     * Initializes a checkout pane.
     *
     * @param {*} pane
     *   The checkout pane.
     */
    async initializeCheckoutPane(pane) {
      const event = new CustomEvent('initializeCheckoutPane', {
        bubbles: true,
        cancelable: false,
        detail: {},
      });
      pane
        .querySelector('button[data-checkout-button-type="continue"]')
        ?.addEventListener('click', async () => {
          await this.closeCheckoutPane(pane, true);
        });
      pane
        .querySelector('button[data-checkout-button-type="edit"]')
        ?.addEventListener('click', async () => {
          await this.openCheckoutPane(pane);
        });
      pane.dataset.initialized = 'true';
      pane.dispatchEvent(event);
    }

    /**
     * Adds listeners to the shipping checkout pane.
     *
     * Ajax calls will bring in a new version of the pane,
     * so we need to be able to add the listeners again.
     *
     * @param {*} pane
     *   The checkout pane.
     */
    addListenersToShippingCheckoutPane(pane) {
      pane.addEventListener('initializeCheckoutPane', async (event) => {
        const shippingPane = event.target;
        const shippingRecalculateButton = shippingPane.querySelector(
          '*[data-drupal-selector="edit-shipping-information-recalculate-shipping"]',
        );
        shippingRecalculateButton.style.display = 'none';
        const editButton = shippingPane.querySelector(
          'button[data-checkout-button-type="edit"]',
        );
        const changeButton = shippingPane.querySelector(
          'button[data-checkout-button-type="change"]',
        );
        const shipmentMethod = shippingPane.querySelector(
          '*[data-drupal-selector="edit-shipping-information-shipments"]',
        );
        if (this.memberAuthenticatedSuccessfully) {
          editButton.style.display = 'none';
          changeButton.style.display = '';
          shipmentMethod.style.display = 'block';
        } else {
          editButton.style.display = '';
          changeButton.style.display = 'none';
          shipmentMethod.style.display = '';
        }
        if (this.settings.shippingInformation?.address) {
          const { address } = this.settings.shippingInformation;
          this.shippingAddress = {
            name: {
              firstName: address.given_name,
              lastName: address.family_name,
            },
            address: {
              countryCode: address.country_code,
              postalCode: address.postal_code,
              adminArea2: address.locality,
              adminArea1: address.administrative_area,
              addressLine1: address.address_line1,
              addressLine2: address.address_line2,
              company: address.organization,
            },
            phoneNumber: {
              countryCode: null,
              nationalNumber: null,
            },
          };
          this.paymentComponent?.setShippingAddress(this.shippingAddress);
        }
        if (this.memberAuthenticatedSuccessfully && this.shippingAddress) {
          if (this.activeCheckoutPaneId === 'shipping_information') {
            await this.openCheckoutPane(this.getNextCheckoutPane(shippingPane));
          }
        }
        shippingPane
          .querySelector('button[data-checkout-button-type="change"]')
          ?.addEventListener('click', async (clickEvent) => {
            clickEvent.preventDefault();
            if (this.memberAuthenticatedSuccessfully) {
              // Open Shipping Address Selector for Fastlane members.
              const { selectionChanged, selectedAddress } =
                await this.profile.showShippingAddressSelector();

              if (selectionChanged) {
                // Update state & form UI.
                this.shippingAddress = selectedAddress;
                await this.populateShippingInformationProfileForm(
                  this.shippingAddress,
                );
                if (
                  this.shippingAddress &&
                  this.shippingAddress.address.postalCode
                ) {
                  this.paymentComponent?.setShippingAddress(
                    this.shippingAddress,
                  );
                }
                await CommercePaypalFastlane.validateCheckoutPane(
                  this.getShippingCheckoutPane(),
                );
              } else {
                // Selection modal was dismissed without selection.
              }
            }
          });
      });

      pane.addEventListener('validateCheckoutPane', async (event) => {
        const shippingPane = event.target;
        const controls = CommercePaypalFastlane.getControls(shippingPane);
        let validated = true;
        /* eslint-disable-next-line no-restricted-syntax */
        for (const control of controls) {
          if (!control.checkValidity()) {
            control.reportValidity();
            control.scrollIntoView({ behavior: 'smooth', block: 'center' });
            event.preventDefault();
            validated = false;
            break;
          }
        }
        if (validated) {
          const shippingRecalculateButton = shippingPane.querySelector(
            '*[data-drupal-selector="edit-shipping-information-recalculate-shipping"]',
          );
          shippingRecalculateButton?.dispatchEvent(new Event('mousedown'));
        }
        return validated;
      });

      pane.addEventListener('updateCheckoutPaneSummary', async (event) => {
        // We don't want the default update to occur.
        event.detail.updated = true;
      });
    }

    /**
     * Open a checkout pane.
     *
     * @param {*} pane
     *   The checkout pane.
     * @return {Promise<boolean>}
     *   Whether the pane was opened.
     */
    async openCheckoutPane(pane) {
      if (this.activeCheckoutPaneId) {
        if (
          !(await this.closeCheckoutPane(
            this.getCheckoutPane(this.activeCheckoutPaneId),
          ))
        ) {
          return false;
        }
      }
      const event = new CustomEvent('openCheckoutPane', {
        bubbles: true,
        cancelable: true,
        detail: {},
      });
      const cancelled = !pane.dispatchEvent(event);
      // If an event handler calls preventDefault(), we will not
      // open the checkout pane.
      if (cancelled) {
        return false;
      }
      pane.classList.add('active');
      CommercePaypalFastlane.focusCheckoutPane(pane);
      this.activeCheckoutPaneId = pane.dataset.checkoutPaneId;
      if (!this.visitedPaneIds.includes(pane.dataset.checkoutPaneId)) {
        this.visitedPaneIds.push(pane.dataset.checkoutPaneId);
      }
      pane.classList.add('visited');
      return true;
    }

    /**
     * Set focus to the first control in the pane.
     *
     * @param {*} pane
     *   The checkout pane.
     */
    static focusCheckoutPane(pane) {
      const firstControl = CommercePaypalFastlane.getControls(pane)[0] ?? pane;
      firstControl?.scrollIntoView({ block: 'center' });
      firstControl?.focus();
    }

    /**
     * Validate a checkout pane.
     *
     * @param {*} pane
     *   The checkout pane.
     * @return {Promise<boolean>}
     *   Whether the pane is valid.
     */
    static async validateCheckoutPane(pane) {
      const event = new CustomEvent('validateCheckoutPane', {
        bubbles: true,
        cancelable: true,
        detail: { validated: null },
      });
      const cancelled = !pane.dispatchEvent(event);
      if (cancelled) {
        return false;
      }
      let { validated } = event.detail;
      // If no event handler set the validated property,
      // we will perform validation of all controls.
      if (validated === null) {
        validated = true;
        const controls = CommercePaypalFastlane.getControls(pane);
        /* eslint-disable-next-line no-restricted-syntax */
        for (const control of controls) {
          if (!control.checkValidity()) {
            control.reportValidity();
            control.scrollIntoView({ block: 'center' });
            validated = false;
            break;
          }
        }
      }
      return validated;
    }

    /**
     * Close a checkout pane.
     *
     * @param {*} pane
     *   The pane to close.
     * @param {boolean} validate
     *   Whether to validate and open the next pane.
     * @return {Promise<boolean>}
     *   Whether the pane was closed.
     */
    async closeCheckoutPane(pane, validate = false) {
      const event = new CustomEvent('closeCheckoutPane', {
        bubbles: true,
        cancelable: true,
        detail: {},
      });
      const cancelled = !pane.dispatchEvent(event);
      if (cancelled) {
        return false;
      }
      if (validate) {
        const validated =
          await CommercePaypalFastlane.validateCheckoutPane(pane);
        if (validated) {
          pane.dataset.validated = 'true';
          await CommercePaypalFastlane.updateCheckoutPaneSummary(pane);
          this.activeCheckoutPaneId = null;
          let nextCheckoutPane = this.getNextCheckoutPane(pane);
          while (
            nextCheckoutPane &&
            (nextCheckoutPane.classList.contains('visited') ||
              nextCheckoutPane.classList.contains('pinned'))
          ) {
            nextCheckoutPane = this.getNextCheckoutPane(nextCheckoutPane);
          }
          if (nextCheckoutPane) {
            await this.openCheckoutPane(nextCheckoutPane);
          } else {
            this.form
              .querySelector('input[data-drupal-selector="edit-actions-next"]')
              .focus();
          }
          pane.classList.remove('active');
        } else {
          delete pane.dataset.validated;
          return false;
        }
      } else {
        pane.classList.remove('active');
      }
      return true;
    }

    /**
     * Update a checkout pane's summary.
     *
     * @param {*} pane
     *   The checkout pane.
     * @return {Promise<boolean>}
     *   Whether the pane was updated.
     */
    static async updateCheckoutPaneSummary(pane) {
      const event = new CustomEvent('updateCheckoutPaneSummary', {
        bubbles: true,
        cancelable: true,
        detail: { updated: null },
      });
      const cancelled = !pane.dispatchEvent(event);
      if (cancelled) {
        return false;
      }
      if (event.detail.updated) {
        return true;
      }
      const controls = CommercePaypalFastlane.getControls(pane);
      let summary = '';
      /* eslint-disable-next-line no-restricted-syntax */
      for (const control of controls) {
        if (control.value) {
          summary += `<div class="field__label">${control.labels[0]?.innerHTML}</div><div class="field__item">${control.value}</div></div>`;
        }
      }
      if (summary) {
        CommercePaypalFastlane.getPaneSummary(pane).querySelector(
          'div',
        ).innerHTML = summary;
      }
      return true;
    }

    /**
     * Initialize the checkout flow.
     *
     * @param {*} settings
     *   The settings.
     */
    async initializeCheckout(settings) {
      const fastlaneOptions = {};
      this.settings = settings;

      this.form = document.querySelector(
        'form.commerce-checkout-flow-paypal-fastlane',
      );
      this.form
        .querySelector(
          '[data-checkout-pane-id="paypal_fastlane_contact_information"]',
        )
        .addEventListener('validateCheckoutPane', async (event) => {
          const emailControl = event.target.querySelector(
            '*[data-drupal-selector="edit-paypal-fastlane-contact-information-email"]',
          );
          if (emailControl.checkValidity()) {
            if (emailControl.value !== this.email) {
              this.email = emailControl.value;
              await this.lookupFastlaneMember(this.email);
              event.detail.validated = true;
            }
          } else {
            emailControl.reportValidity();
            event.preventDefault();
            event.detail.validated = false;
          }
        });

      const shippingPane = this.form.querySelector(
        '[data-checkout-pane-id="shipping_information"]',
      );
      if (shippingPane) {
        this.addListenersToShippingCheckoutPane(shippingPane);
      }

      this.form
        .querySelector('[data-checkout-pane-id="payment_information"]')
        .addEventListener('validateCheckoutPane', async (event) => {
          try {
            this.paymentToken = await this.paymentComponent
              ?.getPaymentToken()
              .catch(() => {
                return null;
              });
            if (!this.paymentToken) {
              event.preventDefault();
            }
          } catch (e) {
            event.preventDefault();
            if (this.activeCheckoutPaneId !== 'payment_information') {
              await this.openCheckoutPane(
                this.getCheckoutPane('payment_information'),
              );
            }
          }
        });

      /* eslint-disable-next-line no-restricted-syntax */
      for (const checkoutPane of this.getCheckoutPanes()) {
        /* eslint-disable-next-line no-await-in-loop */
        await this.initializeCheckoutPane(checkoutPane);
      }

      await this.openCheckoutPane(this.getCheckoutPanes()[0]);

      if (this.settings.allowedBillingCountries?.length) {
        // Restricting billing countries is not currently supported.
      }
      if (
        this.settings.hasShipments &&
        this.settings.allowedShippingCountries?.length
      ) {
        fastlaneOptions.shippingAddressOptions = {
          allowedLocations: settings.allowedShippingCountries,
        };
      }
      if (this.settings.allowedBrands?.length) {
        fastlaneOptions.cardOptions = {
          allowedBrands: this.settings.allowedBrands,
        };
      }
      // Instantiates the Fastlane module.
      const fastlane = await window.paypal.Fastlane(fastlaneOptions);
      // A limited number of languages are supported.
      fastlane.setLocale(this.settings.locale ?? 'en_us');
      const {
        identity,
        profile,
        FastlanePaymentComponent,
        FastlaneWatermarkComponent,
      } = fastlane;
      this.profile = profile;
      this.identity = identity;
      this.FastlanePaymentComponent = FastlanePaymentComponent;
      (
        await FastlaneWatermarkComponent({
          includeAdditionalInfo: true,
        })
      ).render('#watermark-container');

      // Event listener when the user clicks to place the order.
      this.form.addEventListener('submit', async (event) => {
        if (this.allowSubmit) {
          return true;
        }
        event.preventDefault();
        try {
          await this.showOverlay();
          /* eslint-disable-next-line no-restricted-syntax */
          for (const pane of this.getCheckoutPanes()) {
            if (!pane.dataset.validated) {
              /* eslint-disable-next-line no-await-in-loop */
              await this.openCheckoutPane(pane);
            }
          }
          if (!this.paymentToken) {
            this.paymentToken = await this.paymentComponent?.getPaymentToken();
          }
          if (this.paymentToken) {
            this.allowSubmit = true;
          }
          this.form.querySelector('[data-fastlane-payment-token]').value =
            JSON.stringify(this.paymentToken);
          if (this.settings.hasShipments) {
            this.form.querySelector('[data-fastlane-shipping-address]').value =
              JSON.stringify(this.shippingAddress);
          }
          this.form.submit();
        } catch (e) {
          this.overlayModal?.close();
        }
      });
    }

    /**
     * Look up a Fastlane by PayPal member.
     *
     * @param {*} email
     *   The email address.
     */
    async lookupFastlaneMember(email) {
      await this.showOverlay();
      const identityResult = await this.identity.lookupCustomerByEmail(email);
      const { customerContextId } = identityResult;
      this.overlayModal?.close();

      if (customerContextId) {
        // Email is associated with a Fastlane member or a PayPal member,
        // send customerContextId to trigger the authentication flow.
        const authResponse =
          await this.identity.triggerAuthenticationFlow(customerContextId);

        if (authResponse?.authenticationState === 'succeeded') {
          // Fastlane member successfully authenticated themselves
          // profileData contains their profile details
          this.memberAuthenticatedSuccessfully = true;
          this.form.classList.add('member');
          this.profileData = authResponse?.profileData;
          if (this.settings.hasShipments) {
            this.shippingAddress = this.profileData?.shippingAddress ?? null;
          }
          this.paymentToken = this.profileData?.card;
          this.getPaymentCheckoutPane().classList.add('pinned');
        } else {
          this.getPaymentCheckoutPane().classList.remove('pinned');
          // Member failed or canceled authentication.
          // Treat them as a guest payer.
          this.memberAuthenticatedSuccessfully = false;
          this.form.classList.remove('member');
        }
      } else {
        // No profile found with this email address. This is a guest payer.
        this.memberAuthenticatedSuccessfully = false;
        this.form.classList.remove('member');
      }
      if (!this.paymentComponent) {
        const paymentComponentOptions = {
          styles: this.settings.styles,
        };
        this.paymentComponent = await this.FastlanePaymentComponent(
          paymentComponentOptions,
        );
        await this.paymentComponent?.render(
          '*[data-drupal-selector="edit-payment-information-add-payment-method-payment-details-fastlane-payment-container"]',
        );
      }
      if (this.settings.hasShipments) {
        // Populate the shipping information profile pane.
        if (this.memberAuthenticatedSuccessfully) {
          await this.populateShippingInformationProfileForm(
            this.shippingAddress ?? {},
          );
          if (this.shippingAddress && this.shippingAddress.address.postalCode) {
            this.paymentComponent?.setShippingAddress(this.shippingAddress);
          }
          await CommercePaypalFastlane.validateCheckoutPane(
            this.getShippingCheckoutPane(),
          );
        } else {
          const shippingPane = this.getShippingCheckoutPane();
          const editButton = shippingPane.querySelector(
            'button[data-checkout-button-type="edit"]',
          );
          const changeButton = shippingPane.querySelector(
            'button[data-checkout-button-type="change"]',
          );
          const shipmentMethod = shippingPane.querySelector(
            '*[data-drupal-selector="edit-shipping-information-shipments"]',
          );
          editButton.style.display = '';
          changeButton.style.display = 'none';
          shipmentMethod.style.display = '';
        }
      }
    }

    /**
     * Show the overlay.
     */
    async showOverlay() {
      const overlayTitle = Drupal.t('Fastlane by PayPal');
      const overlayContent = Drupal.t('One moment, please!');
      const overlayModal = Drupal.dialog(
        `<div><span>${overlayContent}</span></div>`,
        {
          title: overlayTitle,
          classes: {
            'ui-dialog': 'fastlane-lookup-overlay',
          },
          width: 800,
          resizable: false,
          closeOnEscape: false,
          top: 0,
          left: 0,
          beforeClose: false,
          close() {
            overlayModal.close();
          },
        },
      );
      this.overlayModal = overlayModal;
      this.overlayModal.showModal();
    }

    /**
     * Get the summary container for the pane.
     *
     * @param {*} pane
     *   The checkout pane.
     * @return {*}
     *   The checkout pane summary.
     */
    static getPaneSummary(pane) {
      return pane.querySelector('.summary');
    }

    /**
     * Gets the next checkout pane, following the specified one.
     *
     * @param {*} pane
     *   The checkout pane.
     * @return {*}
     *   The next checkout pane or null, if this is the last one.
     */
    getNextCheckoutPane(pane) {
      const checkoutPanes = this.getCheckoutPanes();
      const currentIndex = Array.prototype.indexOf.call(checkoutPanes, pane);
      return checkoutPanes[currentIndex + 1];
    }

    /**
     * Gets the payment information checkout pane.
     *
     * @return {*}
     *   The payment checkout pane.
     */
    getPaymentCheckoutPane() {
      return this.getCheckoutPane('payment_information');
    }

    /**
     * Gets the shipping information checkout pane.
     *
     * @return {*}
     *   The shipping checkout pane.
     */
    getShippingCheckoutPane() {
      return this.getCheckoutPane('shipping_information');
    }

    /**
     * Return a specific checkout pane, given the checkout pane id.
     * @param {*} checkoutPaneId
     *   The checkout pane id.
     * @return {*}
     *   The checkout pane or null.
     */
    getCheckoutPane(checkoutPaneId) {
      return this.form.querySelector(
        `.layout-region-checkout-main *[data-checkout-pane-id="${checkoutPaneId}"]`,
      );
    }

    /**
     * Return the checkout panes in the main region.
     *
     * @return {*}
     *   The checkout panes in the main region.
     */
    getCheckoutPanes() {
      // We are interested in the panes in the main area, not the sidebar.
      return this.form.querySelectorAll(
        '.layout-region-checkout-main *[data-checkout-pane-id]',
      );
    }

    /**
     * Reinitialize a checkout pane.
     *
     * @param {*} item
     *   The possible pane.
     */
    async reinitializeCheckoutPane(item) {
      let checkoutPane = null;
      if (item.dataset?.checkoutPaneId) {
        checkoutPane = item;
      } else if (item.firstElementChild?.dataset.checkoutPaneId) {
        checkoutPane = item.firstElementChild;
      }
      if (!checkoutPane) {
        return;
      }
      if (checkoutPane.dataset?.checkoutPaneId === 'shipping_information') {
        this.addListenersToShippingCheckoutPane(checkoutPane);
      }
      await this.initializeCheckoutPane(checkoutPane);
      if (checkoutPane.dataset.checkoutPaneId === this.activeCheckoutPaneId) {
        checkoutPane.classList.add('active');
      }
      if (this.visitedPaneIds.includes(checkoutPane.dataset.checkoutPaneId)) {
        checkoutPane.classList.add('visited');
      }
    }

    /**
     * Get the user controls in the container.
     *
     * @param {*} container
     *   The container to search.
     * @return {*}
     *   The controls in the container.
     */
    static getControls(container) {
      return container.querySelectorAll(
        'input:not([type="hidden"]):not([type="submit"]),textarea,select',
      );
    }
  }

  Drupal.behaviors.commercePaypalFastlane = {
    async attach(context, drupalSettings) {
      // Validate all required dependencies.
      if (!Drupal || !drupalSettings) {
        console.error('Required dependencies are not available');
        return;
      }

      const [settings] = Object.values(drupalSettings.paypalFastlane);
      if (!settings?.clientToken || !settings?.elementId) {
        console.error('Required settings are missing');
        return;
      }

      const [element] = once(
        'paypal-fastlane-processed',
        `#${settings.elementId}`,
        context,
      );
      if (element) {
        const fastlaneComponent = new CommercePaypalFastlane();
        await fastlaneComponent.initializeFastlane(settings);
        Drupal.CommerceFastlaneInstances.set(element, fastlaneComponent);
      } else {
        const [fastlaneComponent] = Drupal.CommerceFastlaneInstances.values();
        if (fastlaneComponent) {
          await fastlaneComponent.reinitializeCheckoutPane(context);
        }
      }
    },
  };
})(Drupal, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc