PageSourceSearch

https://wearechange.org/wp-content/plugins/wp-express-checkout/assets/js/public.js

js wearechange.org collected 2026-10-03 21:39:30 UTC 36,964 bytes, 998 lines download raw bytes

1var ppecHandler = function( data ) {
2	this.data = data;
3	this.actions = {};
4
5	const parent = this;
6
7	// TODO: Need to convert this to vanilla js
8	this.processPayment = function( payment, action ) {
9		jQuery.post( ppecFrontVars.ajaxUrl, {
10				'action': action,
11				wp_ppdg_payment: payment,
12				data: parent.data,
13				nonce: parent.data.nonce
14			} )
15			.done( function( data ) {
16				parent.completePayment( data );
17			} );
18	};
19
20	this.completePayment = function( data ) {
21		let ret = true;
22		let dlgTitle = ppecFrontVars.str.paymentCompleted;
23		let dlgMsg = ppecFrontVars.str.redirectMsg;
24		let redirect_url;
25		if ( data.redirect_url ) {
26			redirect_url = data.redirect_url;
27		} else {
28			dlgTitle = ppecFrontVars.str.errorOccurred;
29			dlgMsg = data;
30			ret = false;
31		}
32		document.querySelector( '.wp-ppec-overlay[data-ppec-button-id="' + parent.data.id + '"]' ).style.display = 'none';
33
34		const dialogMsgParagraph = document.createElement('p');
35		dialogMsgParagraph.id = 'wp-ppdg-dialog-msg';
36		dialogMsgParagraph.textContent = dlgMsg;
37		const dialog = document.createElement('div');
38		dialog.id = 'wp-ppdg-dialog-message';
39		dialog.title = dlgTitle;
40		dialog.appendChild(dialogMsgParagraph);
41
42		parent.scCont?.querySelector('.wp-ppec-button-container')?.insertAdjacentElement('afterend', dialog);
43
44		// Fade in dialog
45		dialog.style.display = 'none';
46		dialog.style.opacity = 0;
47		dialog.style.transition = 'opacity 0.5s';
48		dialog.style.display = 'block';
49		setTimeout(() => {
50			dialog.style.opacity = 1;
51		}, 10);
52
53		if ( redirect_url ) {
54			location.href = redirect_url;
55		}
56		return ret;
57	};
58
59	this.ValidatorTos = function( input ) {
60		return input.checked !== true ? ppecFrontVars.str.acceptTos : '';
61	};
62
63	this.ValidatorBilling = function( input ) {
64		let val;
65		if (input.type === 'checkbox'){
66			val = input.checked;
67		} else {
68			val = Boolean(input.value.trim())
69		}
70
71		return parent.isElementVisible(input) && !val ? ppecFrontVars.str.required : null;
72	};
73
74	this.isValidTotal = function() {
75		parent.calcTotal();
76		return !!parent.data.total;
77	};
78
79	this.ValidatorQuantity = function( input ) {
80		const val_orig = input.value;
81		const val = parseInt( val_orig );
82		let error = false;
83		if ( isNaN( val ) || ( val_orig % 1 !== 0 ) || val <= 0 ) {
84			error = ppecFrontVars.str.enterQuantity;
85		} else if ( parent.data.stock_enabled && val > parent.data.stock_items ) {
86			error = ppecFrontVars.str.stockErr.replace( '%d', parent.data.stock_items );
87		} else {
88			parent.data.quantity = val;
89		}
90		return error;
91	};
92
93	this.ValidatorAmount = function( input ) {
94		const val_orig = input.value;
95		const val = parseFloat( val_orig );
96		const min_val = input.getAttribute( 'min' );
97		let error = false;
98		const errMsg = ppecFrontVars.str.enterAmount;
99		if ( !isNaN( val ) && min_val <= val ) {
100			parent.data.orig_price = val;
101			parent.data.price = parent.applyVariations( val );
102		} else {
103			error = errMsg;
104		}
105		return error;
106	};
107
108	if ( this.data.btnStyle.layout === 'horizontal' ) {
109		this.data.btnStyle.tagline = false;
110	}
111
112	this.clientVars = {};
113
114	this.clientVars[ this.data.env ] = this.data.client_id;
115
116	this.inputs = [];
117
118	this.validateInput = function( input, validator ) {
119		if ( !input ) {
120			return false;
121		}
122
123		this.inputs.push( [ input, validator ] );
124
125		input.addEventListener('change', function(e) {
126			parent.displayInputError( this, validator );
127			parent.validateOrder();
128		})
129	};
130
131	this.validateOrder = function() {
132		let enable_actions = true;
133
134		document.querySelectorAll('#wpec_billing_' + parent.data.id + ', #place-order-' + parent.data.id ).forEach(el => {
135			parent.toggleVisibility(el, 'block', !parent.isValidTotal());
136		})
137
138		parent.toggleVisibility(
139			document.getElementById( parent.data.id ),
140			'block',
141			!parent.isElementVisible(document.getElementById( 'place-order-' + parent.data.id ))
142		);
143
144		parent.inputs.forEach( function( inputArr ) {
145			const input = inputArr[ 0 ];
146			const validator = inputArr[ 1 ];
147			if ( validator( input ) ) {
148				enable_actions = false;
149			}
150		} );
151
152		if ( !parent.isValidTotal() ) {
153			enable_actions = false;
154		}
155
156		if ( enable_actions ) {
157			parent.actions.enable();
158		} else {
159			parent.actions.disable();
160		}
161
162		parent.updateAllAmounts();
163
164		// Dispatch a custom event for addons to listen on 'validateOrder' function run.
165		document.dispatchEvent(new CustomEvent('wpec_validate_order', {
166			detail: {
167				ppecHandler: parent
168			}
169		}))
170		jQuery( document ).trigger( 'wpec_validate_order', [ parent ] ); // TODO: Need to remove this jquery version after all addon code is updated to the vanilla version.
171	};
172
173	this.displayInputError = function( input, validator ) {
174		const error = validator( input );
175		const errMsgCont = input.type === 'checkbox' || input.type === 'number' ? input.parentNode.parentNode.querySelector( '.wp-ppec-form-error-msg' ) : input.parentNode.querySelector( '.wp-ppec-form-error-msg' );
176
177		if (error && error.length){
178			input.classList.add('hasError');
179			errMsgCont.style.display = 'block';
180			errMsgCont.innerText = error;
181		}else {
182			input.classList.remove('hasError');
183			errMsgCont.style.display = 'none';
184			errMsgCont.innerText = '';
185		}
186	};
187
188	this.displayErrors = function() {
189		parent.inputs.forEach( function( input ) {
190			parent.displayInputError( input[ 0 ], input[ 1 ] );
191		} );
192	};
193
194	this.scCont = document.querySelector( '.wp-ppec-shortcode-container[data-ppec-button-id="' + parent.data.id + '"]' );
195
196	this.buttonArgs = {
197		env: parent.data.env,
198		client: parent.clientVars,
199		style: parent.data.btnStyle,
200		commit: true,
201		onInit: function( data, actions ) {
202			parent.actions = actions;
203			parent.validateInput( document.getElementById( 'wpec-tos-' + parent.data.id ), parent.ValidatorTos );
204			parent.validateInput( document.querySelector( '#wp-ppec-custom-quantity[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorQuantity );
205			parent.validateInput( document.querySelector( '#wp-ppec-custom-amount[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorAmount );
206			document.querySelectorAll( '#wpec_billing_' + parent.data.id + ' .wpec_required' ).forEach( function(element) {
207				parent.validateInput( element, parent.ValidatorBilling );
208			} );
209			parent.data.orig_price = parseFloat( parent.data.price );
210			parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio' )?.forEach(function (item){
211				item.addEventListener('change', function (e){
212					const grpId = e.currentTarget.getAttribute( 'data-wpec-variations-group-id' );
213					const varId = e.currentTarget.value;
214					if ( Object.getOwnPropertyNames( parent.data.variations ).length !== 0 ) {
215						if ( !parent.data.variations.applied ) {
216							parent.data.variations.applied = [];
217						}
218						parent.data.variations.applied[ grpId ] = varId;
219						parent.data.price = parent.applyVariations( parent.data.orig_price );
220						parent.validateOrder();
221					}
222				})
223			});
224			parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio:checked' )?.forEach(el => {
225				// Dispatch change event to trigger any 'change' event listeners on this element
226				el.dispatchEvent( new Event('change') );
227			})
228
229			parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.addEventListener( 'change', function() {
230				parent.toggleVisibility(parent.scCont.querySelector( '.wpec_shipping_address_container' ), 'inherit');
231				parent.scCont.querySelector( '.wpec_address_wrap' )?.classList.toggle('shipping_enabled');
232			} );
233
234			document.getElementById( 'place-order-' + parent.data.id )?.addEventListener('click', function() {
235				parent.buttonArgs.onClick();
236			} );
237
238			document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id )?.addEventListener('click', async function (e) {
239				e.preventDefault();
240				const wpecCouponBtn = e.currentTarget;
241				const wpecCouponInputField = document.getElementById('wpec-coupon-field-' + parent.data.id)
242				const couponCode = wpecCouponInputField?.value;
243				if (couponCode.trim() === '') {
244					return false;
245				}
246				const wpecCouponSpinner = wpecCouponBtn.querySelector('svg');
247				wpecCouponBtn.setAttribute('disabled', true);
248				wpecCouponSpinner.style.display = 'inline';
249
250				const ajaxData = new URLSearchParams({
251					'action': 'wpec_check_coupon',
252					'product_id': parent.data.product_id,
253					'coupon_code': couponCode,
254					'curr': parent.data.currency,
255					/*'amount': parent.data.item_price,
256					'tax': parent.data.tax,
257					'shipping': parent.data.shipping*/
258				});
259				try {
260					const response = await fetch(ppecFrontVars.ajaxUrl, {
261						method: "post",
262						headers: {
263							'Content-Type': 'application/x-www-form-urlencoded'
264						},
265						body: ajaxData
266					});
267
268					const response_data = await response.json();
269
270					if (response_data.success) {
271						parent.data.discount = response_data.discount;
272						parent.data.discountType = response_data.discountType;
273						parent.data.couponCode = response_data.code;
274
275						document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = '<span class="wpec_coupon_code">' + response_data.discountStr + '</span><button class="wpec_coupon_apply_btn" id="wpec-remove-coupon-' + parent.data.id + '" title="' + ppecFrontVars.str.strRemoveCoupon + '">' + ppecFrontVars.str.strRemove + '</button>'
276						document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'none';
277						document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'none';
278
279						const totalContainers = parent.getPriceContainer();
280						// Note: there is more than one price container in a single product shortcode.
281						totalContainers.forEach(function (totalCont){
282							let totCurr;
283							let totNew;
284							if (totalCont.querySelector('.wpec_price_full_total')) {
285								totCurr = totalCont.querySelector('span.wpec_tot_current_price')
286								totCurr?.classList.add('wpec_line_through');
287								totNew = totalCont.querySelector('span.wpec_tot_new_price');
288							} else {
289								totCurr = totalCont.querySelector('span.wpec_price_amount')
290								totCurr?.classList.add('wpec_line_through');
291								totNew = totalCont.querySelector('span.wpec_new_price_amount');
292							}
293
294							const priceCurr = totalCont.querySelector('span.wpec-price-amount')
295							priceCurr?.classList.add('wpec_line_through');
296
297							const priceNew = totalCont.querySelector('span.wpec-new-price-amount');
298							parent.validateOrder();
299
300							document.getElementById('wpec-remove-coupon-' + parent.data.id)?.addEventListener('click', function (e) {
301								e.preventDefault();
302								document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = '';
303								document.getElementById('wpec-coupon-field-' + parent.data.id).value = '';
304								document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'block';
305								document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'block';
306								totCurr?.classList.remove('wpec_line_through');
307								priceCurr?.classList.remove('wpec_line_through');
308								if (totNew) {
309									totNew.innerHTML = '';
310								}
311								if (priceNew) {
312									priceNew.innerHTML = '';
313								}
314								delete parent.data.discount;
315								delete parent.data.discountType;
316								delete parent.data.couponCode;
317								delete parent.data.discountAmount;
318								delete parent.data.newPrice;
319								parent.validateOrder();
320							});
321						})
322					} else {
323						document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = response_data.msg
324					}
325					wpecCouponSpinner.style.display = 'none';
326					wpecCouponBtn.removeAttribute('disabled');
327
328				} catch (err) {
329					alert(err.message);
330				}
331			})
332
333			const couponField = document.getElementById( 'wpec-coupon-field-' + parent.data.id );
334			couponField?.addEventListener( 'keydown', function( keyboardEvent ) {
335				if ( keyboardEvent.code === 13 ) {
336					keyboardEvent.preventDefault();
337					document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id ).click();
338					return false;
339				}
340			} );
341
342			parent.validateOrder();
343
344			if ( parent.data.errors ) {
345				const errorEl = document.createElement('div');
346				errorEl.className = 'wp-ppec-form-error-msg';
347				errorEl.innerHTML = parent.data.errors;
348
349				parent.scCont.appendChild( errorEl );
350				errorEl.style.display = 'block';
351			}
352
353			document.querySelectorAll( '.wpec-button-placeholder' ).forEach( function (el) {
354				el.remove()
355			})
356		},
357		onClick: function() {
358			const wpecPlaceOrderButtonSection = document.getElementById("place-order-"+parent.data.id);
359			if (wpecPlaceOrderButtonSection){
360				wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'inline';
361				wpecPlaceOrderButtonSection.querySelector("button").setAttribute("disabled", true);
362			}
363
364			parent.displayErrors();
365			// Get first error input if there is any.
366			const errInput = parent.scCont.querySelector( '.hasError' );
367			if ( errInput ) {
368				errInput.focus();
369				errInput.dispatchEvent( new Event('change') );
370
371				if (wpecPlaceOrderButtonSection) {
372					wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'none';
373					wpecPlaceOrderButtonSection.querySelector("button").removeAttribute("disabled");
374				}
375			} else if ( !parent.data.total ) {
376				const same_shipping_billing_address_enabled = parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.checked;
377				const billing_address = {
378					address_line_1: document.getElementById( 'wpec_billing_address-' + parent.data.id )?.value,
379					admin_area_1: document.getElementById( 'wpec_billing_state-' + parent.data.id )?.value,
380					admin_area_2: document.getElementById( 'wpec_billing_city-' 
380+ parent.data.id )?.value,
381					postal_code: document.getElementById( 'wpec_billing_postal_code-' + parent.data.id )?.value,
382					country_code: document.getElementById( 'wpec_billing_country-' + parent.data.id )?.value,
383				};
384				const shipping_address = same_shipping_billing_address_enabled ? billing_address : {
385					address_line_1: document.getElementById( 'wpec_shipping_address-' + parent.data.id )?.value,
386					admin_area_1: document.getElementById( 'wpec_shipping_state-' + parent.data.id )?.value,
387					admin_area_2: document.getElementById( 'wpec_shipping_city-' + parent.data.id )?.value,
388					postal_code: document.getElementById( 'wpec_shipping_postal_code-' + parent.data.id )?.value,
389					country_code: document.getElementById( 'wpec_shipping_country-' + parent.data.id )?.value,
390				};
391
392				const paymentData = {
393					payer: {
394						name: {
395							given_name: document.getElementById( 'wpec_billing_first_name-' + parent.data.id )?.value,
396							surname: document.getElementById( 'wpec_billing_last_name-' + parent.data.id )?.value,
397						},
398						email_address: document.getElementById( 'wpec_billing_email-' + parent.data.id )?.value,
399						address: billing_address,
400						shipping_address: shipping_address,
401					}
402				}
403
404				document.dispatchEvent(new CustomEvent('wpec_process_free_payment', {
405					detail: {
406						paymentData,
407						data: parent.data,
408					}
409				}));
410
411				parent.processPayment(paymentData, 'wpec_process_empty_payment' );
412			}
413		},
414		createOrder: async function( data, actions ) {
415			parent.calcTotal();
416
417			//We need to round to 2 decimal places to make sure that the API call will not fail.
418			let itemTotalValueRounded = (parent.data.price * parent.data.quantity).toFixed(2);
419			let itemTotalValueRoundedAsNumber = parseFloat(itemTotalValueRounded);
420			//console.log('Item total value rounded: ' + itemTotalValueRoundedAsNumber);
421			//console.log(parent.data);
422
423			// Checking if shipping will be required
424			let shipping_pref = 'NO_SHIPPING'; // Default value
425			if (parent.data.shipping !== "" || parent.data.shipping_enable === true) {
426				console.log("The physical product checkbox is enabled or there is shipping value has been configured. Setting the shipping preference to collect shipping.");
427				shipping_pref = 'GET_FROM_FILE';
428			}
429
430			// Create order_data object to be sent to the server.
431			const order_data = {
432				intent: 'CAPTURE',
433				payment_source: {
434					paypal: {
435						experience_context: {
436							payment_method_preference: 'IMMEDIATE_PAYMENT_REQUIRED',
437							shipping_preference: shipping_pref,
438							user_action: 'PAY_NOW',
439						}
440					}
441				},
442				purchase_units: [ {
443					amount: {
444						value: parent.data.total,
445						currency_code: parent.data.currency,
446						breakdown: {
447							item_total: {
448								currency_code: parent.data.currency,
449								value: itemTotalValueRoundedAsNumber
450							}
451						}
452					},
453					items: [ {
454						name: parent.data.name,
455						quantity: parent.data.quantity,
456						unit_amount: {
457							value: parent.data.price,
458							currency_code: parent.data.currency
459						}
460					} ]
461				} ]
462			};
463
464			if ( parent.data.tax ) {
465				order_data.purchase_units[ 0 ].amount.breakdown.tax_total = {
466					currency_code: parent.data.currency,
467					value: parent.PHP_round( parent.data.tax_amount * parent.data.quantity, parent.data.dec_num )
468				};
469				order_data.purchase_units[ 0 ].items[ 0 ].tax = {
470					currency_code: parent.data.currency,
471					value: parent.data.tax_amount
472				};
473			}
474			if ( parent.data.shipping ) {
475				order_data.purchase_units[ 0 ].amount.breakdown.shipping = {
476					currency_code: parent.data.currency,
477					value: parent.getTotalShippingCost(),
478				};
479			}
480			if ( parent.data.discount ) {
481				order_data.purchase_units[ 0 ].amount.breakdown.discount = {
482					currency_code: parent.data.currency,
483					value: parseFloat( parent.data.discountAmount )
484				};
485			}
486			//End of create order_data object.
487			console.log('Order data: ', order_data);
488
489			let nonce = wpec_create_order_vars.nonce;
490
491			let wpec_data_for_create = parent.data;//parent.data is the data object that was passed to the ppecHandler constructor.
492			console.log('WPEC data for create-order: ', wpec_data_for_create);
493
494			let post_data = 'action=wpec_pp_create_order&data=' + encodeURIComponent(JSON.stringify(order_data)) + '&wpec_data=' + encodeURIComponent(JSON.stringify(wpec_data_for_create)) + '&_wpnonce=' + nonce;
495			try {
496				// Using fetch for AJAX request. This is supported in all modern browsers.
497				const response = await fetch( ppecFrontVars.ajaxUrl, {
498					method: "post",
499					headers: {
500						'Content-Type': 'application/x-www-form-urlencoded'
501					},
502					body: post_data
503				});
504
505				const response_data = await response.json();
506
507				if (response_data.order_id) {
508					console.log('Create-order API call to PayPal completed successfully.');
509					return response_data.order_id;
510				} else {
511					const error_message = response_data.err_msg
512					console.error('Error occurred during create-order call to PayPal. ', error_message);
513					throw new Error(error_message);//This will trigger the alert in the "catch" block below.
514				}
515			} catch (error) {
516				console.error(error.message);
517				alert('Could not initiate PayPal Checkout...\n\n' + error.message);
518			}
519		},
520		onApprove: async function( data, actions ) {
521			document.querySelector( 'div.wp-ppec-overlay[data-ppec-button-id="' + parent.data.id + '"]' ).style.display = 'flex';
522
523			console.log('Setting up the AJAX request for capture-order call.');
524			
525			// Create the data object to be sent to the server.
526			let pp_bn_data = {};
527			pp_bn_data.order_id = data.orderID;//The orderID is the ID of the order that was created in the createOrder method.
528			let wpec_data = parent.data;//parent.data is the data object that was passed to the ppecHandler constructor.
529			//console.log('WPEC data (JSON): ' + JSON.stringify(wpec_data));
530
531			let nonce = wpec_on_approve_vars.nonce;
532			let post_data = 'action=wpec_pp_capture_order&data=' + encodeURIComponent(JSON.stringify(pp_bn_data)) + '&wpec_data=' + encodeURIComponent(JSON.stringify(wpec_data)) + '&_wpnonce=' + nonce;
533			try {
534				const response = await fetch( ppecFrontVars.ajaxUrl, {
535					method: "post",
536					headers: {
537						'Content-Type': 'application/x-www-form-urlencoded'
538					},
539					body: post_data
540				});
541
542				const response_data = await response.json();
543
544				console.log('Capture-order API call to PayPal completed successfully.');
545
546				// Call the completePayment method to do any redirection or display a message to the user.
547				parent.completePayment(response_data); 
548
549			} catch (error) {
550				console.error(error);
551				alert('PayPal returned an error! Transaction could not be processed. Enable the debug logging feature to get more details...\n\n' + JSON.stringify(error));
552			}
553
554		},
555		onError: function( err ) {
556			document.getElementById("place-order-"+parent.data.id).querySelector("button>svg").style.display = 'none';
557			document.getElementById("place-order-"+parent.data.id).querySelector("button").removeAttribute("disabled");
558			alert( err );
559		}
560	};
561
562	// TODO: Need to convert this to vanilla js
563	this.formatMoney = function( n ) {
564		var decimalPlaces = isNaN(decimalPlaces = Math.abs(parent.data.dec_num)) ? 2 : parent.data.dec_num;
565		var decimalSeparator = parent.data.dec_sep || ".";
566		var thousandSeparator = parent.data.thousand_sep || ",";
567		var sign = n < 0 ? "-" : "";
568		var integerPart = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(decimalPlaces)));
569		var integerPartLength = integerPart.length;
570		var remainderLength = integerPartLength > 3 ? integerPartLength % 3 : 0;
571
572		var formattedIntegerPart = sign +
573			(remainderLength ? integerPart.substr(0, remainderLength) + thousandSeparator : "") +
574			integerPart.substr(remainderLength).replace(/(\d{3})(?=\d)/g, "$1" + thousandSeparator) +
575			(decimalPlaces ? decimalSeparator + Math.abs(n - integerPart).toFixed(decimalPlaces).slice(2) : "");
576
577		var formats = {
578			left: '{symbol}{price}',
579			left_space: '{symbol} {price}',
580			right: '{price}{symbol}',
581			right_space: '{price} {symbol}'
582		};
583
584		var formattedPrice = formats[parent.data.curr_pos]
585			.replace('{symbol}', parent.data.currency_symbol)
586			.replace('{price}', formattedIntegerPart);
587
588		return formattedPrice;
589	};
590
591	this.getPriceContainer = function() {
592		// Here, we're using 'querySelectorAll' instead of 'querySelector', because there is more than one element.
593		return document.querySelectorAll( '.' + parent.scCont.getAttribute( 'data-price-class' ) );
594	};
595
596	this.getProductItemContainer = function() {
597		// Get the product item container using the price container, so that we don't select other product item container when more than one same product shortcode in a page.
598		const priceContainer = document.querySelector( '.' + parent.scCont.getAttribute( 'data-
598price-class' ) );
599
600		// Selector of the container may vary across different templates. Select the one it can find.
601
602		// For template 1,3
603		let productItemContainer = parent.closestParent(priceContainer, '.wpec-product-item-' + parent.data.product_id );
604
605		// For template 2
606		if (!productItemContainer){
607			productItemContainer = parent.closestParent(priceContainer, '.wpec-post-item-' + parent.data.product_id );
608		}
609
610		// For url payment form
611		if (!productItemContainer){
612			productItemContainer = parent.closestParent(priceContainer, '.wpec-modal-product-' + parent.data.product_id );
613		}
614
615		return productItemContainer;
616	};
617
618	this.updateAllAmounts = function() {
619		parent.calcTotal();
620
621		const productItemContainer = parent.getProductItemContainer();
622
623		if ( productItemContainer ){
624			productItemContainer.querySelectorAll( '.wpec-price-amount' ).forEach(el => el.textContent = parent.formatMoney( parent.data.price ))
625
626			if ( parent.data.quantity > 1 ) {
627				productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'inline')
628				productItemContainer.querySelectorAll( '.wpec-quantity-val' ).forEach(el => el.textContent = parent.data.quantity)
629			} else {
630				productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'none');
631			}
632
633			const total = productItemContainer.querySelectorAll( '.wpec_tot_current_price' );
634			const tot_new = productItemContainer.querySelectorAll( '.wpec_tot_new_price' );
635			const price_new = productItemContainer.querySelectorAll( '.wpec-new-price-amount' );
636
637			if ( typeof parent.data.discountAmount !== "undefined" ) {
638				price_new.forEach(el => el.textContent = parent.formatMoney( parent.data.newPrice ) );
639				tot_new.forEach(el => el.textContent = parent.formatMoney( parent.data.total ) );
640				total.forEach(el => el.textContent = parent.formatMoney( parent.data.subtotal ));
641			} else if ( total.length > 0 ) {
642				total.forEach(el => el.textContent = parent.formatMoney( parent.data.total ));
643			}
644
645			// Update the total shipping cost.
646			const shipping_new = productItemContainer.querySelectorAll( '.wpec_price_shipping_amount' );
647			shipping_new.forEach(el => el.textContent = parent.formatMoney(parent.getTotalShippingCost()));
648
649			// Update the total tax cost.
650			const tax_val = productItemContainer.querySelectorAll( '.wpec-tax-val' );
651			tax_val.forEach(el => el.textContent = parent.formatMoney( parent.data.tax_amount * parent.data.quantity ) );
652		}
653
654		// TODO: Need to convert this to vanilla js
655		jQuery( document ).trigger( 'wpec_after_update_all_amounts', [ parent ] );
656	};
657
658	this.calcTotal = function() {
659		let itemSubt = parseFloat( parent.data.price );
660		let quantity = parseInt( parent.data.quantity );
661		let tAmount = itemSubt * quantity;
662		//We need to round to 2 decimal places to make sure that the API call will not fail.
663		let roundedTotal = tAmount.toFixed(2);//round to 2 decimal places
664		let roundedTotalAsNumber = parseFloat(roundedTotal);//convert to number
665		tAmount = roundedTotalAsNumber;
666		let subtotal = tAmount;
667
668		parent.data.newPrice = itemSubt;
669
670		if ( typeof parent.data.discount !== "undefined" ) {
671			let discountAmount = 0;
672			if ( parent.data.discountType === 'perc' ) {
673				discountAmount = parent.PHP_round( tAmount * parent.data.discount / 100, parent.data.dec_num );
674			} else {
675				discountAmount = data.discount;
676			}
677			if ( discountAmount > tAmount ) {
678				discountAmount = tAmount;
679			}
680			tAmount = tAmount - discountAmount;
681			parent.data.discountAmount = parent.PHP_round( discountAmount, parent.data.dec_num );
682			parent.data.newPrice = parent.PHP_round( itemSubt - discountAmount / quantity, parent.data.dec_num );
683		}
684
685		if ( parent.data.tax ) {
686			let tax = parent.PHP_round( tAmount / quantity * parent.data.tax / 100, parent.data.dec_num );
687			parent.data.tax_amount = tax;
688			tAmount += tax * parent.data.quantity;
689			subtotal += parent.PHP_round( subtotal / quantity * parent.data.tax / 100, parent.data.dec_num ) * parent.data.quantity;
690		}
691
692		const totalShippingCost = parent.getTotalShippingCost();
693		if ( totalShippingCost ) {
694			tAmount += parseFloat( totalShippingCost );			
695			subtotal += parseFloat( totalShippingCost );
696		}
697		parent.data.total = parent.PHP_round( tAmount, parent.data.dec_num );
698		parent.data.subtotal = parent.PHP_round( subtotal, parent.data.dec_num );
699	};
700
701	this.PHP_round = function( num, dec ) {
702		return Math.round( num * Math.pow( 10, dec ) ) / Math.pow( 10, dec );
703	};
704
705	this.applyVariations = function( amount ) {
706		let grpId;
707		if ( parent.data.variations.applied ) {
708			for ( grpId = 0; grpId < parent.data.variations.applied.length; ++grpId ) {
709				let variation = parent.data.variations[ grpId ];
710				amount = amount + parseFloat( variation.prices[ parent.data.variations.applied[ grpId ] ] );
711			}
712		}
713		return parent.PHP_round( amount, parent.data.dec_num );
714	};
715
716	/**
717	 * Calculates the total shipping cost.
718	 * It calculates the per quantity shipping cost along with the base shipping cost of that product.
719	 * 
720	 * @returns {float} The total shipping cost in float of two decimal places. 
721	 */
722	this.getTotalShippingCost = function() {
723		let total = 0;
724		const quantity = parent.data.quantity ? parseInt(parent.data.quantity) : 1;
725		const baseShipping = parent.data.shipping ? parseFloat( parent.data.shipping ) : 0;
726		const shippingPerQuantity = parent.data.shipping_per_quantity ? parseFloat( parent.data.shipping_per_quantity ) : 0;
727	
728		total = baseShipping + (shippingPerQuantity * quantity);
729		// Round to 2 decimal places.
730		total = parseFloat(total.toFixed(2))
731		return total;
732	}
733
734	this.toggleVisibility = function (element, displayVisibilityType = 'block', condition = null) {
735		if (!element){
736			return;
737		}
738
739		// if no condition provided, then the condition will be based on whether the element is visible or not.
740		if (condition === null) {
741			// No specific condition provided.
742			// The content will only visible/invisible if its currently invisible/visible respectively.
743			condition = !parent.isElementVisible(element);
744		}
745
746		// Show / hide the element based on condition.
747		if (condition === true) {
748			element.style.display = displayVisibilityType;
749		} else {
750			element.style.display = 'none';
751		}
752	}
753
754	this.isElementVisible = function (element) {
755		// Check if element exists in the DOM and is an Element node
756		if (!(element instanceof Element)) {
757			return false;
758		}
759
760		// Check if the element has a non-zero bounding box (displayed and has size)
761		const style = getComputedStyle(element);
762		const hasVisibleDisplay = style.display !== 'none';
763		const hasVisibleVisibility = style.visibility !== 'hidden';
764		const hasNonZeroSize = element.offsetWidth > 0 && element.offsetHeight > 0;
765
766		return hasVisibleDisplay && hasVisibleVisibility && hasNonZeroSize;
767	}
768
769	this.closestParent = function (element, selector) {
770		// Traverse up the DOM tree
771		while (element) {
772			// Check if the current element matches the selector
773			if (element.matches(selector)) {
774				return element;
775			}
776			// Move to the parent element
777			element = element.parentElement;
778		}
779		// Return null if no matching parent is found
780		return null;
781	}
782
783	// TODO: Need to convert this to vanilla js
784	jQuery( document ).trigger( 'wpec_before_render_button', [ this ] );
785
786	paypal.Buttons( this.buttonArgs ).render( '#' + parent.data.id );
787
788	// Handle manual checkout functionality if enabled.
789	if (Boolean(parent.data.is_manual_checkout_enabled)){
790		new WpecManualCheckout(parent);
791	}
792};
793
794const wpecModal = function( $ ) {
795	let openModalId = false;
796
797	document.querySelectorAll( '.wpec-modal-open, .wpec-modal-overlay, .wpec-modal-close' ).forEach(function (el){
798		el.addEventListener( 'click', function( e ) {
799			e.preventDefault();
800			toggleModal( this.getAttribute( 'data-wpec-modal' ) );
801		} )
802	});
803
804	document.onkeydown = function( evt ) {
805		evt = evt || window.event;
806		var isEscape = false;
807		if ( "key" in evt ) {
808			isEscape = ( evt.key === "Escape" || evt.key === "Esc" );
809		} else {
810			isEscape = ( evt.keyCode === 27 );
811		}
812		if ( isEscape && openModalId ) {
813			toggleModal();
814		}
815	};
816
817	function toggleModal( modalId ) {
818		modalId = modalId ? modalId : openModalId;
819		openModalId = modalId;
820		var modal = document.getElementById( modalId );
821		modal.classList.toggle( 'wpec-opacity-0' );
822		modal.classList.toggle( 'wpec-pointer-events-none' );
823	}
824
825	document.querySelectorAll('.wpec-custom-number-input button').forEach(button => {
826		button.addEventListener('click', function() {
827			const increment = this.getAttribute('data-action') === 'increment' ? 1 : -1;
828			const customQuantityInput = this.parentElement.querySelector('input');
829			const step = customQuantityInput.getAttribute('step') ? Number(customQuantityInput.getAttribute('step')) : 1;
830			customQuantityInput.value = Number(customQuantityInput.value) + increment * step;
831			customQuantityInput.dispatchEvent(new Event('change'));
832		});
833	});
834
835	// TODO: Need to convert this to vanilla js
836	//shortcode_wpec_show_all_products
837	$("#wpec-sort-by").change(function(){
838		$("#wpec-sort-by-form").submit();
839	});
840};
841
842jQuery( wpecModal );
843
844class WpecManualCheckout {
845
846	constructor(parent) {
847		this.parent = parent;
848		this.buttonId = parent.data.id;
849		this.mcProceedBtn = document.getElementById('wpec-proceed-manual-checkout-'+ this.buttonId);
850		this.mcForm = document.getElementById( 'wpec-manual-checkout-form-' + this.buttonId );
851
852		this.tosCheckbox = document.getElementById( 'wpec-tos-' + this.buttonId );
853		this.customQuantityInput = document.querySelector( '#wp-ppec-custom-quantity[data-ppec-button-id="' + this.buttonId + '"]' );
854		this.customAmountInput = document.querySelector( '#wp-ppec-custom-amount[data-ppec-button-id="' + this.buttonId + '"]' );
855
856		document.addEventListener('wpec_validate_order', (e) => {
857			this.parent.toggleVisibility(
858				document.getElementById( 'wpec-manual-checkout-section-'+ this.buttonId ),
859				'block',
860				!this.parent.isElementVisible(document.getElementById( 'place-order-' + parent.data.id ))
861			);
862		})
863
864		this.init();
865	}
866
867	init(){
868		this.mcForm?.querySelector( '.wpec_same_billing_shipping_enable' )?.addEventListener( 'change', () => {
869			const wpec_address_wrap = this.mcForm?.querySelector( '.wpec_address_wrap' );
870			this.parent.toggleVisibility(wpec_address_wrap?.querySelector( '.wpec_shipping_address_container' ), 'inherit');
871			this.mcForm?.querySelector( '.wpec_address_wrap' )?.classList.toggle('shipping_enabled');
872		} );
873
874		this.parent.validateInput( this.tosCheckbox, this.parent.ValidatorTos );
875		this.parent.validateInput( this.customQuantityInput, this.parent.ValidatorQuantity );
876		this.parent.validateInput( this.customAmountInput, this.parent.ValidatorAmount );
877		this.mcForm?.querySelectorAll( '.wpec_required' ).forEach( (element) => {
878			this.parent.validateInput( element, this.parent.ValidatorBilling );
879		} );
880
881		this.mcForm?.addEventListener('submit', (e) => {
882			e.preventDefault();
883
884			const form = e.target;
885			form.setAttribute("disabled", true);
886
887			const formData = new FormData(form);
888
889			this.parent.displayErrors();
890
891			const mcFormSubmitBtn = form.querySelector('button.wpec-place-order-btn');
892			if (mcFormSubmitBtn){
893				mcFormSubmitBtn.querySelector("svg").style.display = 'inline';
894				mcFormSubmitBtn.setAttribute("disabled", true);
895			}
896
897			// Get first error input if there is any.
898			const errInput = this.getErrorInput();
899
900			if ( errInput ) {
901				errInput.focus();
902				errInput.dispatchEvent( new Event('change') );
903
904				form.removeAttribute("disabled");
905				if (mcFormSubmitBtn) {
906					mcFormSubmitBtn.querySelector("svg").style.display = 'none';
907					mcFormSubmitBtn.removeAttribute("disabled");
908				}
909
910				// Don't continue form submission.
911				return;
912			}
913
914			const billing_address = {
915				address_line_1: formData.get('wpec_billing_address'),
916				admin_area_1: formData.get('wpec_billing_city'),
917				admin_area_2: formData.get('wpec_billing_country'),
918				postal_code: formData.get('wpec_billing_state'),
919				country_code: formData.get('wpec_billing_postal_code'),
920			};
921			const shipping_address = formData.get('wpec_same_billing_shipping_enable') ? billing_address : {
922				address_line_1: formData.get('wpec_shipping_address'),
923				admin_area_1: formData.get('wpec_shipping_city'),
924				admin_area_2: formData.get('wpec_shipping_country'),
925				postal_code: formData.get('wpec_shipping_state'),
926				country_code: formData.get('wpec_shipping_postal_code'),
927			};
928
929			const paymentData = {
930				payer: {
931					name: {
932						given_name: formData.get('wpec_billing_first_name'),
933						surname: formData.get('wpec_billing_last_name'),
934					},
935					email_address: formData.get('wpec_billing_email'),
936					phone: formData.get('wpec_billing_phone'),
937					address: billing_address,
938					shipping_address: shipping_address,
939				}
940			}
941
942			document.dispatchEvent(new CustomEvent('wpec_process_manual_checkout', {
943				detail: {
944					paymentData,
945					data: this.parent.data,
946				}
947			}));
948
949			this.parent.processPayment(paymentData, 'wpec_process_manual_checkout' );
950		} );
951
952		this.mcForm?.addEventListener('reset', (e)=>{
953			const form = e.target;
954			form.removeAttribute("disabled");
955			const mcFormSubmitBtn = form.querySelector('button.wpec-place-order-btn');
956			if (mcFormSubmitBtn){
957				mcFormSubmitBtn.querySelector("svg").style.display = 'none';
958				mcFormSubmitBtn.removeAttribute("disabled");
959			}
960
961			this.toggleManualCheckout();
962		})
963
964		this.mcProceedBtn?.addEventListener('click',  (e) =>{
965			this.toggleManualCheckout();
966		});
967	}
968
969	getErrorInput(){
970		if (this.tosCheckbox?.classList.contains('hasError')){
971			return this.tosCheckbox;
972		} else if (this.customQuantityInput?.classList.contains('hasError')){
973			return this.customQuantityInput;
974		} else if (this.customAmountInput?.classList.contains('hasError')){
975			return this.customAmountInput;
976		} else {
977			return this.mcForm?.querySelector( '.hasError' );
978		}
979	}
980
981	toggleManualCheckout(){
982		this.parent.toggleVisibility(this.mcForm, 'inherit');
983		this.parent.toggleVisibility(this.mcProceedBtn, 'inherit');
984
985		// Toggle paypal btn
986		// this.parent.toggleVisibility(
987		// 	document.getElementById(this.buttonId),
988		// 	'inherit',
989		// 	!this.parent.isElementVisible(this.mcForm)
990		// );
991
992		document.dispatchEvent(new CustomEvent('wpec_toggle_manual_checkout_form', {
993			detail: {
994				wpecManualCheckout: this
995			}
996		}));
997	}
998}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.