Source: listeners/payment-failure.js

import Listener from "./listener";

/**
 * Listens for payment failure errors and sends them to the dataLayer.
 * Uses a `PaymentErrorHandler` object to determine the error type,
 * payment form element, and error element.
 * @class
 * @param {PaymentErrorHandler} errorHandler - An object that contains the errorType, paymentForm, and errorElement
 * @extends Listener
 */
export class PaymentErrorListener extends Listener {
    eventName = "payment_failure";

    constructor(errorHandler) {
        super();
        this.handler = errorHandler;
        this.eventName = errorHandler.errorType || this.eventName;
    }

    listen() {
        this.handleError();
        this.watchForErrors(this.handler.paymentForm);
    }

    /**
     * Trigger the event if an error is displayed
     */
    handleError() {
        const error = this.handler.errorElement;
        if (error) this.trigger(error);
    }

    /**
     * Watch for errors in the payment form
     * This is necessary because errors may be displayed after the page loads
     * @param {HTMLElement | null | undefined} form - The payment form element
     * @return {void}
     */
    watchForErrors(form) {
        if (!form) return;
        const observer = new MutationObserver(() => this.handleError());
        observer.observe(form, { subtree: true, childList: true });
    }

    createPayload(errorElement) {
        return {
            error_message: errorElement.innerText.trim()
        };
    }
}

/**
 * Handles form validation errors on ReCharge checkout
 * @type {PaymentErrorHandler}
 */
export const clientExceptionRechargeHandler = {
    errorType: "client_exception_error",
    get paymentForm() {
        return document.getElementById('payment');
    },
    get errorElement() {
        return document.querySelector('#payment div.field-with-errors span.error-message');
    }
};

/**
 * Handles payment failure errors on ReCharge checkout
 * @type {PaymentErrorHandler}
 */
export const paymentFailureRechargeHandler = {
    get errorElement() {
        const form = document.getElementById('payment');
        const el = form.querySelector('div.field-with-errors span.error-message, div.paypal-error-message');
        return (el && el.style.display !== 'none') ? el : null;
    }
};

/**
 * Handles payment failure errors on Shopify checkout
 * @type {PaymentErrorHandler}
 */
export const paymentFailureShopifyHandler = {
    get errorElement() {
        return document.querySelector('[data-payment-method] .notice--error:not(.hidden)  .notice__text');
    }
};

/**
 * @typedef {Object} PaymentErrorHandler
 * @property {string | undefined} errorType - The event name to trigger
 * @property {HTMLElement | null | undefined} paymentForm - The payment form element
 * @property {HTMLElement | null | undefined} errorElement - The element that contains the error message
 */