# formidable/trunk/square/js/frontend.js

Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes &amp; More, version trunk. 608 lines.

- Page: https://pluginprobe.com/plugins/formidable/trunk/code/square/js/frontend.js
- Raw: https://pluginprobe.com/plugins/formidable/trunk/raw/square/js/frontend.js
- Modified: 2026-08-12T14:56:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/formidable/trunk/code/square/js/frontend.js#L10-L20`.

```javascript
( function() {
	if ( ! window.frmSquareVars ) {
		return;
	}

	const { appId } = frmSquareVars;
	const { locationId } = frmSquareVars;

	// Track the state of the Square card element
	let squareCardElementIsComplete = false;
	let thisForm = null;
	let running = 0;

	let cardGlobal;

	const buyerTokens = {};

	// Track the state of each field in the card form.
	// postalCode is not included by default because Square hides it
	// for cards issued in countries that don't require it (e.g., Australia).
	const cardFields = {
		cardNumber: false,
		expirationDate: false,
		cvv: false
	};

	async function initializeCard( payments ) {
		const cardElement = document.querySelector( '.frm-card-element' );
		if ( ! cardElement ) {
			return;
		}

		const card = await payments.card();
		const cardStyle = frmSquareVars.style;

		// Never attach while the card element is hidden (e.g. by conditional
		// logic). Square measures the container on attach, and a hidden
		// container measures as zero-size, so the card form renders with the
		// wrong height. Wait until the element is visible before attaching.
		await waitForVisibleCardElement( cardElement );

		await card.attach( '.frm-card-element' );

		card.configure( { style: cardStyle } );

		// Track when the postal code field is rendered by Square.
		// Square hides the postal code for cards issued in certain countries.
		card.addEventListener( 'focusClassAdded', event => {
			const { field } = event.detail;
			if ( field === 'postalCode' ) {
				cardFields.postalCode = event.detail.currentState.isCompletelyValid;
			}
		} );

		// Add event listener to track when the card form is valid
		card.addEventListener( 'focusClassRemoved', event => {
			const { field } = event.detail;
			const value = event.detail.currentState.isCompletelyValid;

			cardFields[ field ] = value;

			// Check if all fields are valid
			squareCardElementIsComplete = Object.values( cardFields ).every( item => item === true );

			// Update form submit button based on form validity
			if ( thisForm ) {
				if ( squareCardElementIsComplete ) {
					enableSubmit();
				} else {
					if ( squareIsConditionallyDisabled( thisForm ) ) {
						running = 0;
						enableSubmit();
						return;
					}

					disableSubmit( thisForm );
				}
			}
		} );

		/**
		 * Enable the submit button when postal code is completed.
		 * This is the best we can do for now as Square does not provide
		 * any way of knowing that the credit card was auto-filled.
		 */
		card.addEventListener( 'postalCodeChanged', function( event ) {
			if ( event.detail.currentState.isCompletelyValid ) {
				cardFields.cardNumber = true;
				cardFields.expirationDate = true;
				cardFields.cvv = true;
				cardFields.postalCode = true;
				enableSubmit();
			} else {
				cardFields.postalCode = false;
				squareCardElementIsComplete = false;
				disableSubmit( thisForm );
			}
		} );

		return card;
	}

	/**
	 * Resolve once the card element is visible (has a layout box).
	 * A width of zero means the element or one of its ancestors is hidden,
	 * usually by conditional logic setting display: none.
	 *
	 * @since 6.34
	 *
	 * @param {HTMLElement} cardElement
	 * @return {Promise<void>}
	 */
	function waitForVisibleCardElement( cardElement ) {
		return new Promise( resolve => {
			if ( cardElement.getBoundingClientRect().width > 0 ) {
				resolve();
				return;
			}

			const form = cardElement.closest( 'form' );
			const observer = new MutationObserver( () => {
				if ( cardElement.getBoundingClientRect().width > 0 ) {
					observer.disconnect();
					resolve();
				}
			} );

			// Conditional logic toggles inline styles on field and section
			// containers, so watch the whole form for attribute changes and
			// re-check the card element's visibility on each change.
			observer.observe( form || document.body, {
				attributes: true,
				attributeFilter: [ 'style', 'class' ],
				subtree: true
			} );
		} );
	}

	/**
	 * Check if a Square card element is conditionally hidden.
	 * If it is, we should not be disabling the submit button.
	 *
	 * @since 6.34
	 *
	 * @param {HTMLElement} form
	 *
	 * @return {boolean} True if the field is conditionally hidden, false otherwise.
	 */
	function squareIsConditionallyDisabled( form ) {
		const fieldContainer = getPaymentElementFieldContainer( form );
		if ( ! fieldContainer ) {
			return false;
		}

		// Field is conditionally hidden.
		if ( 'none' === fieldContainer.style.display ) {
			return true;
		}

		// Section parent is conditionally hidden.
		const parentSection = fieldContainer.closest( '.frm_section_heading' );
		return parentSection && 'none' === parentSection.style.display;
	}

	/**
	 * Try to get the field container for a Square card payment element.
	 * The field container is checked to determine if the field is conditionally hidden or not.
	 *
	 * @param {HTMLElement} form
	 *
	 * @return {HTMLElement|null} The field container element or null if not found.
	 */
	function getPaymentElementFieldContainer( form ) {
		const paymentElement = form.querySelector( '.frm-card-element' );
		if ( ! paymentElement ) {
			return null;
		}
		return paymentElement.parentElement.closest( '.frm_form_field' );
	}

	/**
	 * Enable the submit button for the form.
	 *
	 * Square being ready is only half of what enables the button. When the submit
	 * button has conditional logic of its own that is not satisfied, it stays
	 * disabled no matter what state the card is in, so every caller is checked
	 * here rather than at the individual call sites.
	 */
	function enableSubmit() {
		if ( running > 0 ) {
			return;
		}

		if ( submitButtonIsConditionallyDisabled( getFormIdForForm( thisForm ) ) ) {
			return;
		}

		thisForm.classList.add( 'frm_loading_form' );
		frmFrontForm.removeSubmitLoading( jQuery( thisForm ), 'enable', 0 );

		// Trigger custom event for other scripts to hook into
		const event = new CustomEvent( 'frmSquareLiteEnableSubmit', {
			detail: { form: thisForm }
		} );
		document.dispatchEvent( event );
	}

	/**
	 * Disable submit button for a target form.
	 *
	 * @param {Element} form
	 * @return {void}
	 */
	function disableSubmit( form ) {
		jQuery( form ).find( 'input[type="submit"],input[type="button"],button[type="submit"]' ).not( '.frm_prev_page' ).attr( 'disabled', 'disabled' );

		// Trigger custom event for other scripts to hook into
		const event = new CustomEvent( 'frmSquareLiteDisableSubmit', {
			detail: { form }
		} );
		document.dispatchEvent( event );
	}

	async function createPayment( event, token, verificationToken ) {
		const tokenInput = document.createElement( 'input' );
		tokenInput.type = 'hidden';
		tokenInput.value = token;
		tokenInput.setAttribute( 'name', 'square-token' );

		const verificationInput = document.createElement( 'input' );
		verificationInput.type = 'hidden';
		verificationInput.value = verificationToken;
		verificationInput.setAttribute( 'name', 'square-verification-token' );

		// Use the thisForm variable that we set earlier
		if ( thisForm ) {
			thisForm.append( tokenInput );
			thisForm.append( verificationInput );

			if ( typeof frmFrontForm.submitFormManual === 'function' ) {
				frmFrontForm.submitFormManual( event, thisForm );
			} else {
				// Fallback if submitFormManual is not available
				thisForm.submit();
			}
		}
	}

	async function tokenize( paymentMethod ) {
		const tokenResult = await paymentMethod.tokenize();

		if ( tokenResult.status === 'OK' ) {
			return tokenResult.token;
		}

		let errorMessage = `Tokenization failed with status: ${ tokenResult.status }`;
		if ( tokenResult.errors ) {
			errorMessage += ` and errors: ${ JSON.stringify( tokenResult.errors ) }`;
		}

		throw new Error( errorMessage );
	}

	// Required in SCA Mandated Regions: Learn more at https://developer.squareup.com/docs/sca-overview
	async function verifyBuyer( payments, token ) {
		const formData = new FormData( thisForm );
		formData.append( 'action', 'frm_verify_buyer' );
		formData.append( 'nonce', frmSquareVars.nonce );

		// Remove a few fields so form validation does not incorrectly trigger.
		formData.delete( 'frm_action' );
		formData.delete( 'form_key' );
		formData.delete( 'item_key' );

		const response = await fetch( frmSquareVars.ajax, {
			method: 'POST',
			body: formData
		} );

		if ( ! response.ok ) {
			throw new Error( 'Failed to verify buyer' );
		}

		const verificationData = await response.json();
		if ( ! verificationData.success ) {
			throw new Error( verificationData.data );
		}

		if ( buyerTokens[ verificationData.data.hash ] ) {
			// Avoid a second verify buyer request if the verification data has not changed.
			return buyerTokens[ verificationData.data.hash ];
		}

		const { verificationDetails } = verificationData.data;
		const verificationResults = await payments.verifyBuyer( token, verificationDetails );

		buyerTokens[ verificationData.data.hash ] = verificationResults.token;

		return verificationResults.token;
	}

	/**
	 * Display an error message in the payment form.
	 *
	 * @param {string} errorMessage
	 * @return {void}
	 */
	function displayPaymentFailure( errorMessage ) {
		if ( ! thisForm ) {
			return;
		}

		const statusContainer = thisForm.querySelector( '.frm-card-errors' );
		if ( statusContainer ) {
			statusContainer.textContent = errorMessage;
		}
	}

	async function squareInit() {
		// Find the form containing the Square payment element
		const cardContainer = document.querySelector( '.frm-card-element' );
		if ( cardContainer ) {
			thisForm = cardContainer.closest( 'form' );
			if ( thisForm ) {
				listenForFieldMutations( thisForm );
				listenForSubmitButtonMutations( thisForm );

				if ( ! squareIsConditionallyDisabled( thisForm ) ) {
					// Initially disable the submit button until card is valid
					disableSubmit( thisForm );
				}

				// Add event listener for form submission
				thisForm.addEventListener( 'submit', function( event ) {
					if ( squareIsConditionallyDisabled( thisForm ) ) {
						return;
					}

					event.preventDefault();
					event.stopPropagation();

					if ( ! squareCardElementIsComplete ) {
						const statusContainer = thisForm.querySelector( '.frm-card-errors' );
						if ( statusContainer ) {
							statusContainer.textContent = 'Please complete all card details before submitting.';
						}
					} else {
						handlePaymentMethodSubmission( event, cardGlobal );
					}

					return false;
				} );
			}
		}

		let payments;
		try {
			// Square requires HTTPS to work.
			payments = window.Square.payments( appId, locationId );
		} catch ( e ) {
			const statusContainer = document.querySelector( '.frm-card-errors' );
			statusContainer.classList.add( 'missing-credentials', 'frm_error' );
			statusContainer.style.visibility = 'visible';
			statusContainer.textContent = e.message;
			return;
		}

		let card;
		try {
			card = await initializeCard( payments );
		} catch ( e ) {
			console.error( 'Initializing Card failed', e );
			return;
		}

		cardGlobal = card;

		/**
		 * @param {Object} $form
		 * @return {boolean} false if there are errors.
		 */
		function validateFormSubmit( $form ) {
			const errors = frmFrontForm.validateFormSubmit( $form );
			const keys = Object.keys( errors );

			if ( 1 === keys.length && errors[ keys[ 0 ] ] === '' ) {
				// Pop the empty error that gets added by invisible recaptcha.
				keys.pop();
			}

			return 0 === keys.length;
		}

		async function handlePaymentMethodSubmission( event, card ) {
			try {
				thisForm.classList.add( 'frm_js_validate' );

				if ( ! validateFormSubmit( thisForm ) ) {
					return;
				}

				// Increment running counter and disable the submit button
				running++;
				if ( thisForm ) {
					disableSubmit( thisForm );
				}

				const token = await tokenize( card );
				const verificationToken = await verifyBuyer( payments, token );
				await createPayment( event, token, verificationToken );

				// Decrement running counter after successful payment
				running--;
				if ( running === 0 && thisForm ) {
					enableSubmit();
				}
			} catch ( e ) {
				// Decrement running counter and re-enable submit if appropriate
				running--;
				if ( running === 0 && thisForm && squareCardElementIsComplete ) {
					enableSubmit();
				}
				displayPaymentFailure( e.message );
			}
		}
	}

	/**
	 * Possibly toggle on and off the submit button when a Stripe Link payment field is conditionally shown or hidden.
	 *
	 * @since 6.34
	 *
	 * @param {HTMLElement} form
	 * @return {void}
	 */
	function listenForFieldMutations( form ) {
		const fieldContainer = getPaymentElementFieldContainer( form );
		if ( ! fieldContainer ) {
			return;
		}

		observeAttributeMutations( fieldContainer, handleMutation );

		const section = fieldContainer.closest( '.frm_section_heading' );
		if ( section ) {
			observeAttributeMutations( section, handleMutation );
		}

		/**
		 * Handle a style attribute change for either a payment field container
		 * or the field container of its parent section.
		 *
		 * @param {MutationRecord} mutation
		 * @return {void}
		 */
		function handleMutation( mutation ) {
			if ( mutation.attributeName !== 'style' ) {
				return;
			}

			const isFieldVisible = 'none' !== mutation.target.style.display;

			// If field is hidden, enable submit (field is conditionally not required)
			if ( ! isFieldVisible ) {
				thisForm = form;
				running = 0;
				enableSubmit();
				return;
			}

			// Field is now visible, recalculate size and check validation
			if ( cardGlobal ) {
				cardGlobal.recalculateSize();
			}

			const shouldEnable = squareCardElementIsComplete || squareIsConditionallyDisabled( form );
			if ( ! shouldEnable ) {
				disableSubmit( form );
				return;
			}

			thisForm = form;
			running = 0;
			enableSubmit();
		}
	}

	/**
	 * Keep the submit button disabled while the Square card is not ready.
	 *
	 * Conditional logic on the submit button enables it as soon as its own
	 * conditions are met, with no knowledge of the payment field. Watch for that
	 * and disable it again until the card details are complete.
	 *
	 * @since 6.34
	 *
	 * @param {HTMLElement} form
	 * @return {void}
	 */
	function listenForSubmitButtonMutations( form ) {
		const submitButton = form.querySelector( '.frm_final_submit' );
		if ( ! submitButton ) {
			return;
		}

		observeAttributeMutations( submitButton, mutation => {
			if ( mutation.attributeName !== 'disabled' || submitButton.disabled ) {
				return;
			}

			if ( squareCardElementIsComplete || squareIsConditionallyDisabled( form ) ) {
				// Either the card is filled in or it is conditionally hidden, so
				// there is nothing left for Square to wait on.
				return;
			}

			disableSubmit( form );
		} );
	}

	/**
	 * @param {HTMLElement} element
	 * @param {Function}    mutationHandler
	 *
	 * @return {void}
	 */
	function observeAttributeMutations( element, mutationHandler ) {
		const observer = new MutationObserver(
			mutations => {
				mutations.forEach( mutationHandler );
			}
		);
		observer.observe(
			element,
			{ attributes: true }
		);
	}

	/**
	 * Check if the submit button is conditionally disabled.
	 * This is required for Stripe link so the button does not get enabled at the wrong time after completing the Stripe elements.
	 *
	 * @since 6.34
	 *
	 * @param {string} formId
	 *
	 * @return {boolean} True if the submit button is conditionally disabled, false otherwise.
	 */
	function submitButtonIsConditionallyDisabled( formId ) {
		if ( ! submitButtonIsConditionallyNotAvailable( formId ) ) {
			return false;
		}

		// __FRMRULES is only defined when conditional logic is on the page.
		const submitRules = typeof __FRMRULES === 'undefined' ? undefined : __FRMRULES[ `submit_${ formId }` ];

		return Boolean( submitRules ) && 'disable' === submitRules.hideDisable;
	}

	/**
	 * Check submit button is conditionally "hidden". This is also used for the enabled check and is used in submitButtonIsConditionallyDisabled.
	 *
	 * @since 6.34
	 *
	 * @param {string} formId
	 *
	 * @return {boolean} True if the submit button is conditionally not available, false otherwise.
	 */
	function submitButtonIsConditionallyNotAvailable( formId ) {
		const hideFields = document.getElementById( `frm_hide_fields_${ formId }` );
		if ( ! hideFields ) {
			return false;
		}

		// The value is a JSON array of every container conditional logic has
		// hidden, for example ["frm_field_25_container","frm_form_16_container
		// .frm_final_submit"], so match the quoted entry anywhere in it. Matching
		// the array brackets too would only find the submit button when it is the
		// single hidden entry, and it never is once the payment field has
		// conditional logic of its own.
		return hideFields.value.includes( `"frm_form_${ formId }_container .frm_final_submit"` );
	}

	/**
	 * Check a form's form_id input for a form ID value.
	 *
	 * @param {HTMLElement} form
	 *
	 * @return {number} The form ID.
	 */
	function getFormIdForForm( form ) {
		return parseInt( form.querySelector( '[name="form_id"]' ).value );
	}

	document.addEventListener( 'DOMContentLoaded', async function() {
		if ( ! window.Square ) {
			console.error( 'Square.js failed to load properly' );
			return;
		}

		squareInit();

		jQuery( document ).on( 'frmPageChanged', function() {
			squareInit();
		} );
	} );
}() );

```
