Warning: chmod(): No such file or directory in /www/wwwroot/scuipturepactory.com/wp-content/mu-plugins/worksec.php on line 7
Uname:Linux iZt4n9czhka2zvqfajdlr2Z 6.8.0-63-generic #66-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 13 20:25:30 UTC 2025 x86_64

Base Dir : /www/wwwroot/scuipturepactory.com

User : www


403WebShell
403Webshell
Server IP : 47.82.179.145  /  Your IP : 216.73.217.129
Web Server : nginx/1.26.3
System : Linux iZt4n9czhka2zvqfajdlr2Z 6.8.0-63-generic #66-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 13 20:25:30 UTC 2025 x86_64
User : www ( 1001)
PHP Version : 8.3.30
Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /www/wwwroot/scuipturepactory.com/wp-content/plugins.off/sureforms/assets/js/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /www/wwwroot/scuipturepactory.com/wp-content/plugins.off/sureforms/assets/js//stripe-payment.js
/**
 * SureForms Stripe Payment Integration
 *
 * @since 2.0.0
 */
/* global Stripe, srfm_ajax */

class StripePayment {
	// Store Stripe instances
	static stripeInstances = {};
	static paymentElements = {};
	static paymentIntents = {};
	static subscriptionIntents = {};

	// Initialize on page load
	static {
		window.srfmPaymentElements = this.paymentElements;
	}

	/**
	 * Constructor for the Calculations class.
	 * @param {HTMLElement} form - The form element containing calculation fields.
	 */
	constructor( form ) {
		this.form = form;
		// Find all payment blocks within the form.
		const getPaymentFields = this.form.querySelectorAll(
			'.srfm-block.srfm-payment-block'
		);

		// Initialize Stripe payment for each payment field.
		getPaymentFields.forEach( ( field ) => {
			this.processPayment( field );
		} );
	}

	/**
	 * Create payment or subscription intent during form submission.
	 * Unified function that handles both payment types based on the paymentType parameter.
	 *
	 * @param {string}      blockId      - Block ID.
	 * @param {number}      amount       - The amount for the payment/subscription.
	 * @param {HTMLElement} paymentInput - The payment input element.
	 * @param {string}      paymentType  - Payment type: 'one-time' or 'subscription'.
	 * @return {Promise<Object>} Resolves with payment/subscription data.
	 */
	async createPaymentIntentOnSubmission(
		blockId,
		amount,
		paymentInput,
		paymentType = 'one-time'
	) {
		// Setup
		const isSubscription = paymentType === 'subscription';
		const customerData = this.extractCustomerData( paymentInput );

		// Extract common data
		const currency = paymentInput.dataset.currency || 'usd';
		const description =
			paymentInput.dataset.description ||
			( isSubscription
				? PAYMENT_UTILITY.getStripeStrings(
					'sureforms_subscription',
					'SureForms Subscription'
				  )
				: PAYMENT_UTILITY.getStripeStrings(
					'sureforms_payment',
					'SureForms Payment'
				  ) );

		// Build FormData
		const data = new FormData();
		data.append(
			'action',
			isSubscription
				? 'srfm_create_subscription_intent'
				: 'srfm_create_payment_intent'
		);
		data.append( 'nonce', srfm_ajax.payment_nonce );
		// Handle zero-decimal currencies (JPY, KRW, etc.) - don't multiply by 100
		const formattedAmount =
			window?.srfmStripe?.zeroDecimalCurrencies?.includes(
				currency.toUpperCase()
			)
				? parseInt( amount )
				: parseInt( amount * 100 );
		data.append( 'amount', formattedAmount );
		data.append( 'currency', currency );
		data.append( 'description', description );
		data.append( 'block_id', blockId );
		data.append( 'customer_email', customerData.email );
		data.append( 'customer_name', customerData.name );

		// Add form_id for server-side validation
		const formElement = paymentInput.closest( 'form' );
		const formIdInput = formElement?.querySelector(
			'input[name="form-id"]'
		);
		if ( formIdInput?.value ) {
			data.append( 'form_id', formIdInput.value );
		}

		// Add subscription-specific data
		if ( isSubscription ) {
			data.append( 'interval', customerData.interval );
			data.append( 'plan_name', customerData.planName );
		}

		// Make API call
		try {
			const response = await fetch( srfm_ajax.ajax_url, {
				method: 'POST',
				body: data,
			} );

			const responseData = await response.json();

			// Handle success
			if ( responseData.success ) {
				const clientSecret = responseData.data.client_secret;
				const paymentIntentId = responseData.data.payment_intent_id;
				const customerId = responseData?.data?.customer_id || null;

				// Store payment/subscription data
				if ( isSubscription ) {
					const subscriptionId = responseData.data.subscription_id;

					StripePayment.subscriptionIntents[ blockId ] = {
						subscriptionId,
						customerId: customerId || null,
						paymentIntentId,
						amount,
						interval: customerData.interval,
					};
				} else {
					StripePayment.paymentIntents[ blockId ] = {
						paymentIntentId,
						customerId: customerId || null,
					};
				}

				// Update elements with client secret
				const elementData = StripePayment.paymentElements[ blockId ];
				if ( elementData ) {
					// CRITICAL: Store client secret WITHOUT calling elements.update()
					// This preserves user-entered card data
					elementData.clientSecret = clientSecret;
				}

				return { valid: true };
			}

			// Handle failure
			return {
				valid: false,
				message:
					responseData.data?.message ||
					responseData.data ||
					PAYMENT_UTILITY.getStripeStrings(
						'payment_unavailable',
						'Payment is currently unavailable. Please contact the site administrator.'
					),
			};
		} catch ( error ) {
			return {
				valid: false,
				message:
					error.message ||
					PAYMENT_UTILITY.getStripeStrings(
						'payment_unavailable',
						'Payment is currently unavailable. Please contact the site administrator.'
					),
			};
		}
	}

	processPayment( field ) {
		const paymentInput = field.querySelector( 'input.srfm-payment-input' );

		if ( ! paymentInput ) {
			return;
		}

		const blockId = field.getAttribute( 'data-block-id' );
		// Check payment type from data attribute
		const paymentType =
			paymentInput.getAttribute( 'data-payment-type' ) || 'one-time';

		// Initialize Stripe elements using unified function
		this.initializePaymentElements( blockId, paymentInput, paymentType );
	}

	/**
	 * Initialize Stripe elements for one-time payments or subscriptions.
	 * Unified function that handles both payment types based on the paymentType parameter.
	 *
	 * @param {string}      blockId      - Block ID.
	 * @param {HTMLElement} paymentInput - The payment input element.
	 * @param {string}      paymentType  - Payment type: 'one-time' or 'subscription'.
	 * @return {void} This function does not return a value.
	 */
	initializePaymentElements(
		blockId,
		paymentInput,
		paymentType = 'one-time'
	) {
		// CRITICAL: Check if elements already exist to prevent re-initialization
		// Re-mounting elements destroys user-entered card data
		if ( StripePayment.paymentElements[ blockId ] ) {
			return;
		}

		const stripeKey = paymentInput.dataset.stripeKey;

		if ( ! stripeKey ) {
			return;
		}

		const elementContainer = paymentInput
			.closest( '.srfm-block' )
			.querySelector( '.srfm-stripe-payment-element' );

		if ( ! elementContainer ) {
			return;
		}

		// Initialize Stripe
		if ( ! StripePayment.stripeInstances[ blockId ] ) {
			StripePayment.stripeInstances[ blockId ] = Stripe( stripeKey );
		}

		const stripe = StripePayment.stripeInstances[ blockId ];

		// Build elements configuration based on payment type
		const elementsConfig = {
			mode: paymentType === 'subscription' ? 'subscription' : 'payment',
			currency: paymentInput.dataset.currency || 'usd',
			amount: 12000,
			appearance: {
				theme: 'stripe',
				variables: {
					colorPrimary: '#0073aa',
					colorBackground: '#ffffff',
					colorText: '#424242',
					colorDanger: '#df1b41',
					spacingUnit: '4px',
					borderRadius: '4px',
					fontFamily: '"Manrope", sans-serif',
				},
			},
			fields: {
				billingDetails: {
					email: 'auto', // ✅ Email + Link enabled
				},
			},
		};

		// Add type-specific configuration
		if ( paymentType === 'one-time' ) {
			elementsConfig.captureMethod = 'manual';
		}

		// Create and mount payment element
		const elements = stripe.elements( elementsConfig );
		const paymentElement = elements.create( 'payment' );
		paymentElement.mount( elementContainer );

		// Store references (structure varies by payment type)
		const storedData = {
			stripe,
			elements,
			paymentElement,
			clientSecret: null, // Will be set when payment/subscription intent is created
			paymentType,
		};

		StripePayment.paymentElements[ blockId ] = storedData;

		// Update window object
		window.srfmPaymentElements = StripePayment.paymentElements;

		// Setup event handlers
		this.setupPaymentElementEvents( paymentElement );
	}

	/**
	 * Setup event handlers for payment element.
	 *
	 * @param {Object} paymentElement - The Stripe payment element.
	 * @return {void} This function does not return a value.
	 */
	setupPaymentElementEvents( paymentElement ) {
		// Ready event
		paymentElement.on( 'ready', () => {} );

		// Change event (handling differs by payment type)
		paymentElement.on( 'change', () => {
			// Lets manage the event.error, event.complete events if required.
		} );
	}

	/**
	 * Get payment amount based on amount type (fixed or user-defined).
	 *
	 * @param {HTMLElement} paymentInput - The payment input element.
	 * @return {number|false} The payment amount in dollars, or false if invalid.
	 */
	static getPaymentAmount( paymentInput ) {
		const amountType = paymentInput.dataset.amountType || 'fixed';
		let amount = 0;

		if ( amountType === 'fixed' ) {
			// Get fixed amount from data attribute
			amount = parseFloat( paymentInput.dataset.fixedAmount || 0 );
		} else {
			// Get the format type for dynamic amounts
			const formatType =
				paymentInput.getAttribute(
					'data-dynamic-amount-format-type'
				) || 'us-style';
			const rawAmount = paymentInput.dataset.currentAmount || 0;

			// Normalize the amount based on format type
			amount = StripePayment.normalizeAmount( rawAmount, formatType );
		}

		// Validate the amount - must be valid, not negative, and greater than 0
		if ( isNaN( amount ) ) {
			return false;
		}

		if ( amount < 0 ) {
			return false;
		}

		if ( amount <= 0 ) {
			return false;
		}

		// Additional validation using existing method
		if ( ! StripePayment.validatePaymentAmount( amount ) ) {
			return false;
		}

		return amount;
	}

	/**
	 * Extract customer data from form fields or use dummy data.
	 *
	 * @param {HTMLElement} paymentInput - The payment input element.
	 * @return {Object} An object containing name, email, interval, and planName.
	 */
	extractCustomerData( paymentInput ) {
		const form = paymentInput.closest( 'form' );
		const block = paymentInput.closest( '.srfm-block' );

		// Get subscription plan data from input attributes
		const planName =
			paymentInput.dataset.subscriptionPlanName ||
			PAYMENT_UTILITY.getStripeStrings(
				'subscription_plan',
				'Subscription Plan'
			);
		const interval = paymentInput.dataset.subscriptionInterval || 'month';

		// Use static methods to extract customer data from mapped form fields
		const customerName =
			StripePayment.extractBillingName( form, block ) ||
			PAYMENT_UTILITY.getStripeStrings(
				'sureforms_customer',
				'SureForms Customer'
			);
		const customerEmail =
			StripePayment.extractBillingEmail( form, block ) ||
			PAYMENT_UTILITY.getStripeStrings(
				'customer_example_email',
				'customer@example.com'
			);

		return {
			name: customerName,
			email: customerEmail,
			interval,
			planName,
		};
	}

	/**
	 * Static method to create payment intent for a payment block during form submission.
	 * This should be called from the form submission handler.
	 *
	 * @param {HTMLFormElement} form         - The form element.
	 * @param {HTMLElement}     paymentBlock - The payment block element.
	 * @return {Promise<Object>} Resolves with payment intent or subscription intent data.
	 */
	static async createPaymentIntentsForForm( form, paymentBlock ) {
		const paymentInput = paymentBlock.querySelector(
			'input.srfm-payment-input'
		);
		const paymentType =
			paymentInput.getAttribute( 'data-payment-type' ) || 'one-time';

		if ( ! paymentInput ) {
			return {
				valid: false,
				message: PAYMENT_UTILITY.getStripeStrings(
					'payment_unavailable',
					'Payment is currently unavailable. Please contact the site administrator.'
				),
			};
		}

		// Get payment amount using helper method
		const amount = StripePayment.getPaymentAmount( paymentInput );

		if ( false === amount ) {
			return {
				valid: false,
				message: PAYMENT_UTILITY.getStripeStrings(
					'payment_amount_not_configured',
					'Payment is currently unavailable. Please contact the site administrator to configure the payment amount.'
				),
			};
		} else if ( amount <= 0 ) {
			return {
				valid: false,
				message: PAYMENT_UTILITY.getStripeStrings(
					'payment_amount_not_configured',
					'Payment is currently unavailable. Please contact the site administrator to configure the payment amount.'
				),
			};
		}

		try {
			// Create a temporary instance to call the method
			const tempInstance = new StripePayment( form );
			const blockId = paymentBlock.getAttribute( 'data-block-id' );

			// Use unified function for both payment types
			const result = await tempInstance.createPaymentIntentOnSubmission(
				blockId,
				amount,
				paymentInput,
				paymentType
			);

			return {
				blockId,
				paymentType,
				valid: true,
				...result,
			};
		} catch ( error ) {
			return {
				valid: false,
				message:
					error.message ||
					PAYMENT_UTILITY.getStripeStrings(
						'payment_unavailable',
						'Payment is currently unavailable. Please contact the site administrator.'
					),
			};
		}
	}

	/**
	 * Validate payment amount before processing
	 * @param {number} amount - The amount to validate.
	 * @return {boolean} True if the amount is valid, false otherwise.
	 */
	static validatePaymentAmount( amount ) {
		// Stripe minimum is $0.50 for most currencies
		const minAmount = 0.5;
		const maxAmount = 999999.99; // Reasonable maximum

		if ( isNaN( amount ) || amount < minAmount ) {
			return false;
		}
		if ( amount > maxAmount ) {
			return false;
		}
		return true;
	}

	/**
	 * Normalize amount based on number format type (EU-style or US-style)
	 * @param {string|number} amount     - The amount to normalize.
	 * @param {string}        formatType - The format type: 'eu-style' or 'us-style'.
	 * @return {number} The normalized amount as a number.
	 */
	static normalizeAmount( amount, formatType = 'us-style' ) {
		// If already a number, return it
		if ( typeof amount === 'number' ) {
			return amount;
		}

		// Convert to string and trim
		const amountStr = String( amount ).trim();

		if ( formatType === 'eu-style' ) {
			// EU-style: 1.234,56 (period = thousands, comma = decimal)
			// Remove periods (thousands separator) and replace comma with period (decimal)
			return parseFloat(
				amountStr.replace( /\./g, '' ).replace( ',', '.' )
			);
		}

		// US-style (default): 1,234.56 (comma = thousands, period = decimal)
		// Remove commas (thousands separator)
		return parseFloat( amountStr.replace( /,/g, '' ) );
	}

	/**
	 * Extract billing name from form fields
	 * @param {HTMLElement} form         - The form element.
	 * @param {HTMLElement} paymentBlock - The payment block wrapper element.
	 * @return {string} The extracted billing name or a default value.
	 */
	static extractBillingName( form, paymentBlock ) {
		// Get the customer name field slug from payment input data attribute
		const paymentInput = paymentBlock.querySelector(
			'input.srfm-payment-input'
		);
		const customerNameFieldSlug = paymentInput
			? paymentInput.getAttribute( 'data-customer-name-field' )
			: null;

		if ( ! customerNameFieldSlug || customerNameFieldSlug.trim() === '' ) {
			return '';
		}

		// Find the actual name input field in the form using the slug
		const nameInput = form.querySelector(
			`.srfm-input-block.srfm-slug-${ customerNameFieldSlug } .srfm-input-common`
		);

		if ( ! nameInput ) {
			return '';
		}

		// Return the trimmed value
		return nameInput.value.trim() || '';
	}

	/**
	 * Extract billing email from form fields
	 * @param {HTMLElement} form         - The form element.
	 * @param {HTMLElement} paymentBlock - The payment block wrapper element.
	 * @return {string} The extracted billing email or a default value.
	 */
	static extractBillingEmail( form, paymentBlock ) {
		// Get the customer email field slug from payment input data attribute
		const paymentInput = paymentBlock.querySelector(
			'input.srfm-payment-input'
		);
		const customerEmailFieldSlug = paymentInput
			? paymentInput.getAttribute( 'data-customer-email-field' )
			: null;

		if (
			! customerEmailFieldSlug ||
			customerEmailFieldSlug.trim() === ''
		) {
			return '';
		}

		// Find the actual email input field in the form using the slug
		const emailInput = form.querySelector(
			`.srfm-email-block.srfm-slug-${ customerEmailFieldSlug } .srfm-input-common`
		);

		if ( ! emailInput ) {
			return '';
		}

		// Return the trimmed value
		return emailInput.value.trim() || '';
	}

	/**
	 * Confirm payment for a specific block
	 * @param {string}      blockId     - The block ID.
	 * @param {Object}      paymentData - The payment data.
	 * @param {HTMLElement} form        - The form element.
	 * @return {Promise<string>} The payment intent or setup intent ID if successful.
	 */
	static async srfmConfirmPayment( blockId, paymentData, form ) {
		const { elements } = paymentData;

		// Validate card details AFTER payment intent is created but BEFORE confirmation
		// This is the correct timing to avoid card data loss
		const { error: submitError } = await elements.submit();

		if ( submitError ) {
			return {
				valid: false,
				error: submitError.message,
				message: submitError.message,
			};
		}

		// Handle payment confirmation via unified handler
		try {
			return await StripePayment.confirmStripePayment(
				blockId,
				paymentData,
				form
			);
		} catch ( error ) {
			// Catch any errors thrown by confirmStripePayment and return consistent structure
			return {
				valid: false,
				error: error.message || error,
				message:
					error.message ||
					PAYMENT_UTILITY.getStripeStrings(
						'payment_failed',
						'Payment failed'
					),
			};
		}
	}

	static async confirmStripePayment( blockId, paymentData, form ) {
		const { stripe, elements, clientSecret, paymentType } = paymentData;

		// Get the payment block element
		const paymentBlock = form.querySelector(
			`[data-block-id="${ blockId }"]`
		);
		// Update form input with subscription data for backend processing
		const paymentInput = paymentBlock.querySelector(
			'.srfm-payment-input'
		);

		const amountType =
			paymentInput.getAttribute( 'data-amount-type' ) || 'fixed';

		// Prepare billing details using StripePayment class methods
		const billingDetails = {
			name: StripePayment.extractBillingName( form, paymentBlock ),
			email: StripePayment.extractBillingEmail( form, paymentBlock ),
		};

		const stripeArgs = {
			elements,
			clientSecret,
			confirmParams: {
				return_url: window.location.href,
				payment_method_data: {
					billing_details: billingDetails,
				},
			},
			redirect: 'if_required',
		};

		const paymentResult = await ( paymentType === 'subscription'
			? stripe.confirmSetup( stripeArgs )
			: stripe.confirmPayment( stripeArgs ) );

		if ( paymentResult?.error ) {
			console.warn( { 'Payment Confirmation Error': paymentResult } );

			const getErrorCode =
				paymentResult?.error?.decline_code ||
				paymentResult?.error?.code;
			// Get the user-friendly message for the decline code
			const errorMessage =
				PAYMENT_UTILITY.getStripeStrings( getErrorCode );

			return {
				valid: false,
				error: paymentResult.error,
				message: errorMessage,
				...paymentResult,
			};
		}

		if (
			'one-time' === paymentType &&
			! [ 'succeeded', 'requires_capture' ].includes(
				paymentResult?.paymentIntent?.status
			)
		) {
			const errorMessage = PAYMENT_UTILITY.getStripeStrings(
				'payment_could_not_be_completed',
				'Payment could not be completed. Please try again or contact the site administrator.'
			);

			return {
				valid: false,
				error: errorMessage,
				message: errorMessage,
				paymentResult,
			};
		}

		const amount = StripePayment.getPaymentAmount( paymentInput );

		const resultArgs = {
			paymentResult,
			blockId,
			paymentType,
			amountType,
			amount,
			paymentInput,
			billingDetails,
		};

		StripePayment.prepareInputValueData( resultArgs );

		return { valid: true };
	}

	/**
	 * Prepares and sets the payment input value data as a JSON string.
	 *
	 * @param {Object}           args               - The configuration arguments.
	 * @param {string}           args.blockId       - The payment block ID.
	 * @param {string}           args.paymentType   - The type of payment ('subscription' or 'one-time').
	 * @param {string}           args.amountType    - The type of amount ('fixed' or 'user-defined').
	 * @param {number}           args.amount        - The payment amount.
	 * @param {HTMLInputElement} args.paymentInput  - The input field to store payment data.
	 * @param {Object}           args.paymentResult - The result object from Stripe payment confirmation.
	 */
	static prepareInputValueData( args ) {
		const {
			blockId,
			paymentType,
			amountType,
			amount,
			paymentInput,
			paymentResult,
			billingDetails,
		} = args;

		const value = {
			blockId,
			amountType,
			amount,
			...( billingDetails || {} ),
		};

		if ( 'subscription' === paymentType ) {
			const subscriptionData =
				StripePayment.subscriptionIntents[ blockId ];
			const getSubscriptionName = paymentInput.getAttribute(
				'data-subscription-plan-name'
			);
			const getSubscriptionBillingCycles = paymentInput.getAttribute(
				'data-subscription-billing-cycles'
			);
			const getSubscriptionInterval = paymentInput.getAttribute(
				'data-subscription-interval'
			);
			value.subscriptionPlanName = getSubscriptionName;
			value.subscriptionBillingCycles = getSubscriptionBillingCycles;
			value.subscriptionInterval = getSubscriptionInterval;

			value.paymentId = paymentResult?.setupIntent?.payment_method;
			value.setupIntent = paymentResult?.setupIntent?.id;
			value.subscriptionId = subscriptionData?.subscriptionId;
			value.customerId = subscriptionData?.customerId;
			value.paymentType = 'stripe-subscription';
			value.status = 'succeeded';
		} else {
			const paymentData = StripePayment.paymentIntents[ blockId ];
			const customerId = paymentData?.customerId || null;
			value.paymentId = paymentResult?.paymentIntent?.id;
			value.paymentType = 'stripe';
			value.customerId = customerId || null;
		}

		paymentInput.value = JSON.stringify( value );
	}
}

// Make StripePayment available globally for form submission
window.StripePayment = StripePayment;

const PAYMENT_UTILITY = {
	currentForm: null,
	amountPlaceHolder: '',
	init: ( form ) => {
		PAYMENT_UTILITY.currentForm = form;
		PAYMENT_UTILITY.amountPlaceHolder = PAYMENT_UTILITY.getStripeStrings(
			'amount_placeholder',
			'Please complete the form to view the amount.'
		);
		PAYMENT_UTILITY.listenAmountChanges();
	},
	/**
	 * Format a number according to the format type (EU-style or US-style)
	 * @param {number|string} amount     - The amount to format
	 * @param {string}        formatType - The format type: 'eu-style' or 'us-style'
	 * @return {string} The formatted number string
	 */
	formatNumberByType: ( amount, formatType = 'us-style' ) => {
		// Normalize to a number first
		const normalizedAmount = StripePayment.normalizeAmount(
			amount,
			formatType
		);

		if ( isNaN( normalizedAmount ) ) {
			return '0.00';
		}

		// Format to 2 decimal places
		const fixedAmount = normalizedAmount.toFixed( 2 );

		if ( formatType === 'eu-style' ) {
			// EU-style: 1.234,56 (period = thousands, comma = decimal)
			const parts = fixedAmount.split( '.' );
			const integerPart = parts[ 0 ].replace(
				/\B(?=(\d{3})+(?!\d))/g,
				'.'
			);
			const decimalPart = parts[ 1 ];
			return integerPart + ',' + decimalPart;
		}

		// US-style (default): 1,234.56 (comma = thousands, period = decimal)
		const parts = fixedAmount.split( '.' );
		const integerPart = parts[ 0 ].replace( /\B(?=(\d{3})+(?!\d))/g, ',' );
		const decimalPart = parts[ 1 ];
		return integerPart + '.' + decimalPart;
	},

	/**
	 * Format subscription message by replacing {amount} placeholder with formatted amount
	 * @param {string} messageFormat   - The message format template (e.g., "{amount} per day for 8 payments")
	 * @param {number} amount          - The payment amount
	 * @param {string} currencySymbol  - The currency symbol (e.g., "$")
	 * @param {string} inputFormatType - The format type: 'eu-style' or 'us-style'
	 * @return {string} Formatted message
	 */
	formatSubscriptionMessage: (
		messageFormat,
		amount,
		currencySymbol,
		inputFormatType = 'us-style'
	) => {
		if ( ! messageFormat ) {
			return PAYMENT_UTILITY.amountPlaceHolder;
		}

		// Format amount with currency using the appropriate number format and position
		const formattedNumber =
			! amount || amount <= 0
				? ''
				: PAYMENT_UTILITY.formatNumberByType( amount, inputFormatType );

		const formattedAmount =
			'' !== formattedNumber
				? PAYMENT_UTILITY.formatAmountWithCurrencyPosition(
					currencySymbol,
					formattedNumber
				  )
				: '';

		// Replace {amount} placeholder with formatted amount
		return '' !== formattedAmount
			? messageFormat.replace( '{amount}', formattedAmount )
			: PAYMENT_UTILITY.amountPlaceHolder;
	},
	updatePaymentBlockAmount: (
		paymentInput,
		amount,
		inputFormatType = 'us-style'
	) => {
		const getPlaceHolderElement = paymentInput
			.closest( '.srfm-block' )
			.querySelector( '.srfm-payment-value' );
		if ( getPlaceHolderElement ) {
			const getCurrencySymbol = getPlaceHolderElement.getAttribute(
				'data-currency-symbol'
			);
			const messageFormat = getPlaceHolderElement.getAttribute(
				'data-message-format'
			);

			if ( getCurrencySymbol ) {
				// Check if message format exists (for subscription messages)
				if ( messageFormat ) {
					const formattedMessage =
						PAYMENT_UTILITY.formatSubscriptionMessage(
							messageFormat,
							amount,
							getCurrencySymbol,
							inputFormatType
						);
					getPlaceHolderElement.innerHTML = formattedMessage;
				} else {
					// Fallback to simple amount display (backward compatible)
					// Format the amount according to the number format type and currency position
					const formattedNumber = PAYMENT_UTILITY.formatNumberByType(
						amount,
						inputFormatType
					);

					getPlaceHolderElement.innerHTML =
						PAYMENT_UTILITY.formatAmountWithCurrencyPosition(
							getCurrencySymbol,
							formattedNumber
						);
				}
			}
		}

		const normalizedAmount = StripePayment.normalizeAmount(
			amount,
			inputFormatType
		);

		paymentInput.setAttribute( 'data-current-amount', normalizedAmount );
		paymentInput.setAttribute(
			'data-dynamic-amount-format-type',
			inputFormatType
		);
	},
	listenAmountChanges: () => {
		const paymentInputs = PAYMENT_UTILITY.currentForm.querySelectorAll(
			'.srfm-block.srfm-payment-block input.srfm-payment-input[data-variable-amount-field]'
		);

		if ( paymentInputs.length > 0 ) {
			for ( let i = 0; i < paymentInputs.length; i++ ) {
				const paymentInput = paymentInputs[ i ];
				const getBlockMappedSlug = paymentInput.getAttribute(
					'data-variable-amount-field'
				);

				if ( getBlockMappedSlug ) {
					const getMappedBlock =
						PAYMENT_UTILITY.currentForm.querySelector(
							`.srfm-block.srfm-slug-${ getBlockMappedSlug }`
						);
					if ( getMappedBlock ) {
						// Check block type.
						if (
							getMappedBlock.classList.contains(
								'srfm-number-block'
							)
						) {
							const getMappedBlockInput =
								getMappedBlock.querySelector(
									'input.srfm-input-common'
								);
							if ( getMappedBlockInput ) {
								getMappedBlockInput.addEventListener(
									'input',
									( event ) => {
										const getMappedBlockInputValue =
											event.target.value;
										// Get format type from the number input's data attribute
										const inputFormatType =
											getMappedBlockInput.getAttribute(
												'format-type'
											) || 'us-style';
										PAYMENT_UTILITY.updatePaymentBlockAmount(
											paymentInput,
											getMappedBlockInputValue,
											inputFormatType
										);
									}
								);
								// Get initial format type for the initial value
								const inputFormatType =
									getMappedBlockInput.getAttribute(
										'format-type'
									) || 'us-style';
								PAYMENT_UTILITY.updatePaymentBlockAmount(
									paymentInput,
									getMappedBlockInput.value,
									inputFormatType
								);
							}
						} else if (
							getMappedBlock.classList.contains(
								'srfm-dropdown-block'
							)
						) {
							const hiddenInput = getMappedBlock.querySelector(
								'.srfm-input-dropdown-hidden'
							);
							if ( hiddenInput ) {
								hiddenInput.addEventListener( 'change', () => {
									const amount =
										PAYMENT_UTILITY.getDropdownAmount(
											getMappedBlock,
											hiddenInput
										);
									PAYMENT_UTILITY.updatePaymentBlockAmount(
										paymentInput,
										amount
									);
								} );
								// Set initial value
								const initialAmount =
									PAYMENT_UTILITY.getDropdownAmount(
										getMappedBlock,
										hiddenInput
									);
								PAYMENT_UTILITY.updatePaymentBlockAmount(
									paymentInput,
									initialAmount
								);
							}
						} else if (
							getMappedBlock.classList.contains(
								'srfm-multi-choice-block'
							)
						) {
							const hiddenInput = getMappedBlock.querySelector(
								'.srfm-input-multi-choice-hidden'
							);
							if ( hiddenInput ) {
								hiddenInput.addEventListener( 'change', () => {
									const amount =
										PAYMENT_UTILITY.getMultiChoiceAmount(
											getMappedBlock,
											hiddenInput
										);
									PAYMENT_UTILITY.updatePaymentBlockAmount(
										paymentInput,
										amount
									);
								} );
								// Set initial value
								const initialAmount =
									PAYMENT_UTILITY.getMultiChoiceAmount(
										getMappedBlock,
										hiddenInput
									);
								PAYMENT_UTILITY.updatePaymentBlockAmount(
									paymentInput,
									initialAmount
								);
							}
						}
					}
				}
			}
		}
	},
	getCurrencySymbol: ( currencyCode ) => {
		// Use localized currency data from PHP
		const currenciesData = window.srfmStripe?.currenciesData || {};
		const upperCurrencyCode = currencyCode?.toUpperCase();
		const currencyData = currenciesData[ upperCurrencyCode ];

		// Return symbol from localized data, or fallback to currency code
		return currencyData?.symbol || currencyCode;
	},
	/**
	 * Get currency sign position from settings
	 * @return {string} Currency sign position ('left', 'right', 'left_space', 'right_space')
	 */
	getCurrencySignPosition: () => {
		return window.srfmStripe?.currencySignPosition || 'left';
	},
	/**
	 * Format amount with currency symbol based on position setting
	 * @param {string}        currencySymbol  - The currency symbol (e.g., "$")
	 * @param {string|number} formattedAmount - The formatted amount string
	 * @param {string}        position        - Currency sign position (optional, defaults to setting)
	 * @return {string} Formatted amount with currency symbol in correct position
	 */
	formatAmountWithCurrencyPosition: (
		currencySymbol,
		formattedAmount,
		position = null
	) => {
		const signPosition =
			position || PAYMENT_UTILITY.getCurrencySignPosition();

		switch ( signPosition ) {
			case 'right':
				return `${ formattedAmount }${ currencySymbol }`;
			case 'left_space':
				return `${ currencySymbol } ${ formattedAmount }`;
			case 'right_space':
				return `${ formattedAmount } ${ currencySymbol }`;
			case 'left':
			default:
				return `${ currencySymbol }${ formattedAmount }`;
		}
	},
	/**
	 * Get amount from dropdown block based on selected option values
	 * @param {HTMLElement} dropdownBlock - The dropdown block element
	 * @param {HTMLElement} hiddenInput   - The hidden input containing selected values
	 * @return {number} The total amount from selected options
	 */
	getDropdownAmount: ( dropdownBlock, hiddenInput ) => {
		const selectedValues = [];
		const hiddenInputValue = hiddenInput.value;

		if ( ! hiddenInputValue ) {
			return 0;
		}

		const { extractValue, normalizeDashes } =
			window.srfm?.srfmUtility || {};

		// Extract selected values from hidden input (format: "Option 1 | Option 2")
		const selectedOptions = extractValue
			? extractValue( hiddenInputValue )
			: hiddenInputValue.split( '|' ).map( ( v ) => v.trim() );

		// Get all dropdown options
		const options = dropdownBlock.querySelectorAll(
			'.srfm-dropdown-input option[option-value]'
		);

		selectedOptions.forEach( ( selectedOption ) => {
			options.forEach( ( option ) => {
				const optionText = normalizeDashes( option.innerText?.trim() );
				const selectedOptionText = normalizeDashes(
					selectedOption?.trim()
				);

				if ( optionText === selectedOptionText ) {
					const optionValue = option.getAttribute( 'option-value' );
					// Only add numeric values
					if ( ! isNaN( optionValue ) ) {
						selectedValues.push( parseFloat( optionValue ) );
					} else if ( '' === optionValue ) {
						selectedValues.push( 0 );
					}
				}
			} );
		} );

		// Sum all selected option values
		return selectedValues.length > 0
			? selectedValues.reduce( ( sum, value ) => sum + value, 0 )
			: 0;
	},
	/**
	 * Get amount from multi-choice block based on selected option values
	 * @param {HTMLElement} multiChoiceBlock - The multi-choice block element
	 * @param {HTMLElement} hiddenInput      - The hidden input containing selected values
	 * @return {number} The total amount from selected options
	 */
	getMultiChoiceAmount: ( multiChoiceBlock, hiddenInput ) => {
		const selectedValues = [];
		const hiddenInputValue = hiddenInput.value;

		if ( ! hiddenInputValue ) {
			return 0;
		}

		const { extractValue } = window.srfm?.srfmUtility || {};

		// Extract selected values from hidden input (format: "Option 1 | Option 2")
		const selectedOptions = extractValue( hiddenInputValue );

		// Get all multi-choice options
		const choices = multiChoiceBlock.querySelectorAll(
			'.srfm-multi-choice-single'
		);

		selectedOptions.forEach( ( selectedOption ) => {
			choices.forEach( ( choice ) => {
				const label = choice.querySelector(
					'.srfm-option-container label'
				);
				if ( label?.innerText?.trim() === selectedOption?.trim() ) {
					const input = choice.querySelector(
						'.srfm-input-multi-choice-single'
					);
					const optionValue = input?.getAttribute( 'option-value' );
					// Only add numeric values
					if ( ! isNaN( optionValue ) ) {
						selectedValues.push( parseFloat( optionValue ) );
					} else if ( '' === optionValue ) {
						selectedValues.push( 0 );
					}
				}
			} );
		} );

		// Sum all selected option values
		return selectedValues.length > 0
			? selectedValues.reduce( ( sum, value ) => sum + value, 0 )
			: 0;
	},
	getStripeStrings: ( code, defaultMessage = '' ) => {
		// If no code provided
		if ( ! code || code === null || code === undefined ) {
			// Return default message if provided, otherwise unknown error
			return defaultMessage && defaultMessage.trim() !== ''
				? defaultMessage
				: window.srfmStripe?.strings?.unknown_error ||
						'An unknown error occurred. Please try again or contact the site administrator.';
		}

		// Check if code exists in localized strings
		const localizedMessage = window.srfmStripe?.strings?.[ code ];

		if ( localizedMessage ) {
			// Code found in localized strings, return it
			return localizedMessage;
		}

		// Code not found in localized strings
		// Return default message if provided, otherwise unknown error
		return defaultMessage && defaultMessage.trim() !== ''
			? defaultMessage
			: window.srfmStripe?.strings?.unknown_error ||
					'An unknown error occurred. Please try again or contact the site administrator.';
	},
};

window.srfmPaymentUtility = PAYMENT_UTILITY;

/**
 * Initializes StripePayment for forms after SureForms initialization event.
 */
document.addEventListener( 'srfm_form_after_initialization', ( event ) => {
	const form = event?.detail?.form;
	if ( form ) {
		// Check if form has payment blocks before initializing
		const paymentBlocks = form.querySelectorAll(
			'.srfm-block.srfm-payment-block'
		);
		if ( paymentBlocks.length > 0 ) {
			new StripePayment( form );
			PAYMENT_UTILITY.init( form );
		}
	}
} );

Youez - 2016 - github.com/yon3zu
LinuXploit