PageSourceSearch

https://volumeonetickets.org/js/orders.1788259773.js

js volumeonetickets.org collected 2026-10-03 19:38:53 UTC 136,515 bytes, 3,750 lines download raw bytes

1var allowSeatsBookedWarning = true;
2
3// True from the order submission POST until that order or its payment fails.
4let orderSubmissionInProgress = false;
5var ticketInsuranceJsLoaded = false;
6var orderData;
7var hasRunCalculate = false;
8var paymentElementLastStatus;
9
10var isInitialPageLoad = true;
11var localCartData;
12
13//fallback in case translated string not in memory
14if (typeof origSubmitButtonText == 'undefined') {
15	var origSubmitButtonText = 'Submit Order';
16	var qtyNotAvailTitle = 'Quantity Not Available';
17	var qtyNotAvailMsg = 'Sorry, one or more items you chose are no longer available. Order adjusted.';
18}
19
20// when the page shows (even if from BACK button)
21$(window).on("pageshow", function() {
22	$('#TicketTypeSelect').val('');
23	if(typeof iframeSubmission === undefined){
24		$('input[name^="data[Ticket]["][name$="[quantity]"]').val('0');
25	}
26	if($('#promo_field').val() != ''){
27		$("label[for='promo_field']").addClass('label-hover');
28		$("label[for='promo_field_sm']").addClass('label-hover');
29	}
30});
31
32// this was being set at the bottom of the Orders::tickets view
33var questionnaireNotRequired = false;
34
35
36const beforeUnloadListener = (event) => {
37	event.preventDefault();
38	return event.returnValue = "Are you sure you want to exit?";
39};
40function warnAgainstLeaveAttempts() {
41	window.addEventListener("beforeunload", beforeUnloadListener, {capture: true});
42}
43function ignoreLeaveAttempts() {
44	window.removeEventListener("beforeunload", beforeUnloadListener, {capture: true});
45}
46
47window.addEventListener('message', function(event) {
48	if (event.data == 'tsModalReloadNeeded') {
49		disableModalUntilReload();
50	}
51});
52
53/**
54 * Builds HTML for a standard cart item row in the order summary
55 * @param {string} params.removeIndex - Index for removal
56 * @param {string} params.formId - Form ID
57 * @param {string} params.aittId - Activity Instance Ticket Type ID
58 * @param {number} params.quantity - Ticket quantity
59 * @param {string} params.typeName - Ticket type name
60 * @param {string} params.activityInfo - Activity information HTML
61 * @param {string} params.timeConflict - Time conflict warning HTML
62 * @param {string} params.currencySymbol - Currency symbol
63 * @param {string} params.selectedQuantityPrice - Formatted price
64 * @param {string} params.seatLabelArgs - Seating label arguments
65 * @returns {string} HTML string for cart item row
66 */
67function buildBackendOrEExOrderSummaryItemRow(params) {
68	const { removeIndex, formId, aittId, quantity, typeName, activityInfo, timeConflict, currencySymbol, selectedQuantityPrice, seatLabelArgs } = params;
69
70	let content =
71		`<div class="table-row">
72			<div class="table-cell del">`;
73				if (typeof seatLabelArgs !== 'undefined') {
74					content +=
75					`<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(${seatLabelArgs});">
76						<i class="fa fa-times" aria-hidden="true"></i>
77					</a>`;
78				} else {
79					content +=
80					`<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), '${removeIndex}', '${formId}', '${aittId}');">
81						<i class="fa fa-times" aria-hidden="true"></i>
82					</a>`;
83				}
84
85			content +=
86			`</div>
87			<div class="table-cell type">
88				<strong>${quantity}x ${typeName}</strong>`;
89
90			if (activityInfo != '') {
91				content += '<br><div class="details">' + timeConflict + activityInfo + '</div>';
92			}
93
94			content += '</div>';
95
96	content += `<div class="table-cell price">${formatForCurrency(currencySymbol, selectedQuantityPrice)}</div></div>`;
97
98	return content;
99}
100
101function disableModalUntilReload() {
102	$('#ts-modal .ts-modal-reload-required').fadeIn('fast');
103}
104
105// DOCUMENT READY
106$(document).ready(function() {
107
108	// this is used by the processes to remove unselected ticket types from being submitted.
109	initializeTicketQuantityInteractionTracking();
110
111	// checks if the JS for ticket insurance is loaded used below
112	ticketInsuranceJsLoaded = (typeof updateInsuranceQuote === "function");
113
114	if(typeof tsTheme == 'undefined') {
115		tsTheme = 'Classic';
116	}
117
118	origSubmitButtonText = $('#orderFormSubmitButton').html();
119	if (typeof origProcessingButtonText == 'undefined') {
120		//fallback, just in case
121		var origProcessingButtonText = "Processing...";
122	}
123
124	// checks for linked ticket types and gates them accordingly
125	updateLinkedTypes()
126
127	//Let's use this way of assigning the action so we can unhook the action
128	//on the comp page when necessary to upload a csv instead of placing direct order
129	$('body').on('submit', '#orderForm', ajaxSubmit);
130
131	function ajaxSubmit() {
132
133		//FORMERLY $('#orderForm').on("submit", function() {, but needed to bind it differently
134		disableSubmit(origProcessingButtonText);
135		allowSeatsBookedWarning = false;
136
137		//KLUDGE:: for IOS issue with radio button not being able be focused on if required.
138		if(ticketInsuranceJsLoaded && $('#OrderPolicyTotal').val() != 0){
139			var field = '[name="data[Order][insurance_opted_in]"]';
140			// check if there was a selection
141			if ($(field + ':checked').length) {
142				// at least one of the radio buttons was check
143			} else {
144				// no radio button was checked
145				alert('Please select a Refund Protection option');
146				$('.ti-radioOptions').css({
147					'margin':'10px 0px',
148					'padding':'10px',
149					'border':'2px solid #FF1200',
150					'border-radius': '10px'
151				});
152				var field = $(field);
153				field.trigger("focus");
154				$('body').scrollTop(field.offset().top);
155				enableSubmit(origSubmitButtonText);
156				return false;
157			}
158		}
159
160		//if it is a cash transaction
161		if($('#OrderCashOption').val() && $('#OrderCashOption').prop('checked') == true){
162			return submitOrder();
163		}
164
165		//if it is free tickets or voucher
166		if($('#OrderProposedTotal').val() <= 0.00 || $('#OrderPurchaseLocation').val() == 'voucher'){
167			return submitOrder();
168		}
169
170		// checks if email is formatted correctly if not iframe and not empty, if the
171		// email field is required, it should not get here empty.
172		if ($('#OrderSource').val() != 'iframe' && $('#OrderEmail').val().length != 0){
173			var testEmail = /^[A-Z0-9._%+-]+@([A-Z0-9-]+\.)+[A-Z]{2,4}$/i;
174			if (!testEmail.test($('#OrderEmail').val())){
175				alertify.error('Email address entered is not valid');
176				enableSubmit(origSubmitButtonText);
177				$('#OrderEmail').trigger("focus");
178				return false;
179			}
180		}
181
182		switch(paymentGateway){
183			case "stripe":
184				var paymentType = $('#OrderPaymentType').val();
185				var $form = $(this);
186				$form.find('button').prop('disabled', true); // Disable the submit button to prevent repeated clicks
187				submitOrder();
188				return false;
189				break;
190			case "authnet":
191				var $form = $(this);
192				$form.find('button').prop('disabled', true); // Disable the submit button to prevent repeated clicks
193				var isWaitlist = false;
194				if($('#OrderStatus').val() == 6){
195					isWaitlist = true;
196				}
197				if(isWaitlist){
198					submitOrder();
199				} else {
200					sendPaymentDataToAnet();
201				}
202				return false;
203				break;
204			case "square":
205				requestCardNonce(event);
206				break;
207			default:
208				return submitOrder();
209				break;
210		}
211	}
212
213	// keeps the form from submitting w/ the enter key
214	// prevent default behavior for enter key unless it's a textarea
215	$(window).on("keydown",function(event) {
216		if (event.key == 'Enter' && event.target.nodeName != 'TEXTAREA') {
217			event.preventDefault();
218			return false;
219		}
220	});
221
222	updateCountry();
223
224	// forces the charity to not accept non-numeric input
225	var charityKeyupTimeout = false;
226	$('.charity [name$="[total]"]').on("keyup", function() {
227		if(isNaN(parseFloat($(this).val()))) { // check if number was entered
228			$(this).val('');
229		}
230		clearTimeout(charityKeyupTimeout);
231		charityKeyupTimeout = setTimeout(function() { $('.charity [name$="[total]"]').trigger("change"); }, 2000); //calculate_order_total();
232	});
233
234	$('.charity [name$="[total]"]').on("blur", function() {
235		clearTimeout(charityKeyupTimeout);
236	});
237
238	//forces the charity input to be 2 decimal places
239	$('.charity [name$="[total]"]').on("change",function() {
240		clearTimeout(charityKeyupTimeout);
241		$(this).val(parseFloat($(this).val()).toFixed(2));
242	});
243
244	// Hide the less info/more info box when the other is shown
245	$(document).on('show.bs.collapse', '[id*=more-info],[id*=less-info]', function() {
246		$(this).parent().find('[id*=more-info],[id*=less-info]').not(this).collapse('hide');
247	});
248
249	//select a ticket type in single dropdown (compact view)
250	$('#TicketTypeSelect').on("change", function(e) {
251		var id = $(this).val();
252		// $('.ticket-type-quantity-selector').hide().val(1); // sets quantity for all of them the 1st item in their respective list
253		$('.ticket-type-quantity-selector').hide();
254		$('.ticket-type-price-area').hide();
255		$('.ticket-type-price-area select option:first-child').attr("selected", "selected");
256		$('.ticket-type-quantity-selector option:first-child').attr("selected", "selected");
257		$('.ticket .reqmsg').hide();
258		$('[id^="ticket-type-description-"]').slideUp();
259		if(id) {
260			// check if qty available or has sub types
261			if ($('#TicketType-' + id + '-quantitySelector').children().length != 0 || $('#ticketTypePricesArea-'+id).length > 0) {
262				$('#TicketType-' + id + '-quantitySelector').fadeIn('fast');
263				$('#ticketTypePricesArea-'+id).show();
264				$('#ticketOptionsAddButtonArea').fadeIn('fast');
265			} else { // no qty available due to linked ticked not present
266				var linkmessage = $('.ticket select[name*="[quantity-general-' + id + '"]').attr('data-link-message');
267				$('#TicketType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
268				$('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast');
269				$('#ticketOptionsAddButtonArea').fadeOut('fast');
270				$('#ticket-tip').fadeOut('fast');
271			}
272			$('#ticket-type-description-' + id).slideDown('fast');
273			$('#ticket-tip').fadeIn('fast');
274		} else {
275			$('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast');
276			$('#ticketOptionsAddButtonArea').fadeOut('fast');
277			$('#ticket-tip').fadeOut('fast');
278		}
279	});
280
281	//select a merchandise type
282	$('#MerchandiseTypeSelect').on("change", function(e) {
283		var id = $(this).val();
284		// $('.merchandise-type-quantity-selector').hide().val(1); // sets quantity for all of them the 1st item in their respective list
285		$('.merchandise-type-quantity-selector').hide();
286		$('.merchandise-type-price-area').hide();
287		$('.merchandise-type-price-area select option:first-child').attr("selected", "selected");
288		$('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected");
289		$('.merchandise .reqmsg').hide();
290		$('.merchandise [id^="merchandise-type-description-"]').slideUp();
291		if(id) {
292			// check if qty available or has sub types
293			if ($('#MerchandiseType-' + id + '-quantitySelector').children().length != 0 || $('#merchandiseTypePricesArea-'+id).length > 0) {
294				$('#MerchandiseType-' + id + '-quantitySelector').fadeIn('fast');
295				$('#merchandiseTypePricesArea-'+id).show();
296				$('#merchandiseOptionsAddButtonArea').fadeIn('fast');
297			} else { // no qty available due to linked ticked not present
298				var linkmessage = $('.merchandise select[name*="[quantity-general-' + id + '"]').attr('data-link-message');
299				$('#MerchandiseType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
300				$('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast');
301				$('#merchandiseOptionsAddButtonArea').fadeOut('fast');
302				$('#merchandise-tip').fadeOut('fast');
303			}
304			$('#merchandise-type-description-' + id).slideDown('fast');
305			$('#merchandise-tip').fadeIn('fast');
306		} else {
307			$('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast');
308			$('#merchandiseOptionsAddButtonArea').fadeOut('fast');
309			$('#merchandise-tip').fadeOut('fast');
310		}
311	});
312
313	//select a ticket type on wide format
314	$('#TicketTypeSelectWide').on("change", function(e) {
315		var id = $(this).val();
316		$('.ticket .ticket-type-quantity-selector').hide();
317		$('.ticket .ticket-type-price-area').hide();
318		$('.ticket .ticket-type-price-area select option:first-child').attr("selected", "selected");
319		$('.ticket .ticket-type-quantity-selector option:first-child').attr("selected", "selected");
320		$('.ticket .reqmsg').hide();
321		if(id) {
322			// check if qty available or has sub types
323			if ($('#TicketType-' + id + '-quantitySelector').children().length != 0 || $('#ticketTypePricesArea-'+id).length > 0) {
324				$('#TicketType-' + id + '-quantitySelector').fadeIn('fast');
325				$('#ticketTypePricesArea-'+id).show();
326				$('#ticketOptionsAddButtonArea').fadeIn('fast');
327				$('#ticket-tip').fadeIn('fast');
328			} else { // no qty available due to linked ticked not present
329				var linkmessage = $('.ticket select[name*="[quantity-general-' + id + '"]').attr('data-link-message');
330				$('#TicketType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
331				$('[id^=TicketType-][id$=-quantitySelector]').fadeOut('fast');
332				$('#ticketOptionsAddButtonArea').fadeOut('fast');
333				$('#ticket-tip').fadeOut('fast');
334			}
335			$('#ticket-tip').fadeIn('fast');
336		} else {
337			$('.ticket [id^=TicketType-][id$=-quantitySelector]').fadeOut('fast');
338			$('#ticketOptionsAddButtonArea').fadeOut('fast');
339			$('#ticket-tip').fadeOut('fast');
340		}
341
342		$('.compact').hide();
343		$('#compact-'+id).show();
344	});
345
346	$('#MerchandiseTypeSelectWide').on("change", function(e) {
347		var id = $(this).val();
348		$('.merchandise-type-quantity-selector').hide();
349		$('.merchandise-type-price-area').hide();
350		$('.merchandise-type-price-area select option:first-child').attr("selected", "selected");
351		$('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected");
352		$('.merchandise .reqmsg').hide();
353		if(id) {
354			// check if qty available or has sub types
355			if ($('#MerchandiseType-' + id + '-quantitySelector').children().length != 0 || $('#merchandiseTypePricesArea-'+id).length > 0) {
356				$('#MerchandiseType-' + id + '-quantitySelector').fadeIn('fast');
357				$('#merchandiseTypePricesArea-'+id).show();
358				$('#merchandiseOptionsAddButtonArea').fadeIn('fast');
359				$('#merchandise-tip').fadeIn('fast');
360			} else { // no qty available due to linked ticked not present
361				var linkmessage = $('.merchandise select[name*="[quantity-general-' + id + '"]').attr('data-link-message');
362				$('#MerchandiseType-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
363				$('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast');
364				$('#merchandise OptionsAddButtonArea').fadeOut('fast');
365				$('#merchandise-tip').fadeOut('fast');
366			}
367			$('#merchandise-tip').fadeIn('fast');
368		} else {
369			$('.merchandise [id^=MerchandiseType-][id$=-quantitySelector]').fadeOut('fast');
370			$('#merchandise OptionsAddButtonArea').fadeOut('fast');
371			$('merchandise-tip').fadeOut('fast');
372		}
373
374		$('.compact').hide();
375		$('#compact-'+id).show();
376	});
377
378	//select a ticket type price/option
379	$('.ticket-type-price-select').on("change", function(e) {
380		var id = $(this).val();
381		// $('.ticket-type-price-area').hide();
382		$('.ticket-type-quantity-selector').hide();
383		$('.ticket-type-quantity-selector option:first-child').attr("selected", "selected");
384		$('.ticket .reqmsg').hide();
385		if(id) {
386			if ($('#TicketTypePrice-' + id + '-quantitySelector').children().length != 0) {
387				$('#TicketTypePrice-' + id + '-quantitySelector').fadeIn('fast');
388			} else {
389				// none available due to linked ticket not present
390				$('#ticketOptionsAddButtonArea').hide();
391				var tickettypeid = $(this).attr('id').replace('TicketTypePriceOptions-','');
392				var linkmessage = $('.ticket select[name*="[quantity-general-' + tickettypeid + '"]').attr('data-link-message');
393				$('#TicketTypePrice-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
394				$(this).val('');
395			}
396		}
397	});
398
399	 //select a ticket type price/option
400	$('.merchandise-type-price-select').on("change", function(e) {
401		var id = $(this).val();
402		// $('.ticket-type-price-area').hide();
403		$('.merchandise-type-quantity-selector').hide();
404		$('.merchandise-type-quantity-selector option:first-child').attr("selected", "selected");
405		$('.merchandise .reqmsg').hide();
406		if(id) {
407			if ($('#MerchandiseTypePrice-' + id + '-quantitySelector').children().length != 0) {
408				$('#MerchandiseTypePrice-' + id + '-quantitySelector').fadeIn('fast');
409			} else {
410				// none available due to linked ticket not present
411				$('#merchandiseOptionsAddButtonArea').hide();
412				var tickettypeid = $(this).attr('id').replace('MerchandiseTypePriceOptions-','');
413				var linkmessage = $('.merchandise select[name*="[quantity-general-' + tickettypeid + '"]').attr('data-link-message');
414				$('#MerchandiseTypePrice-' + id + '-quantitySelector').after("<p class='reqmsg sold-out-text'><strong>"+linkmessage+"</strong></p>");
415				$(this).val('');
416			}
417		}
418	});
419
420	// when the "Add >" button is pressed
421	$('.ticketOptionsAddButton').on("click", function(e) {
422		e.preventDefault();
423
424		var type = "ticketType";
425		var maxTicketTypePriceQuantity = 0;
426		var ticketTypeId = $('#TicketTypeSelect').val();
427		var ticketTypePriceId;
428		var maxTicketTypeQuantity = $("#OrderTicketTypeTmpValues-" + ticketTypeId).attr("data-ticket-type-qty-max");
429
430		var quantitySelector = $('#TicketType-' + ticketTypeId + '-quantitySelector');
431
432		if(typeof(quantitySelector.val()) == "undefined") {
433			ticketTypePriceId = $('#ticketTypePricesArea-'+ticketTypeId+' select:visible').val();
434			quantitySelector = $('#TicketTypePrice-' + ticketTypePriceId + '-quantitySelector');
435			type = "ticketTypePrice";
436		}
437
438		row = quantitySelector.attr('data-ticket-type-row-key');
439		var existingTypeCombinedQuantity = getTotalQuantityOfSpecificType(ticketTypeId);
440		var existingQuantity = parseInt($('#Ticket' + row + 'Quantity').val());
441		var numToAdd = quantitySelector.val();
442
443		if(type == 'ticketTypePrice') {
444			maxTicketTypePriceQuantity = parseInt($('#TicketTypePrice-' + ticketTypePriceId + '-quantitySelector option:last-child').val());
445		}
446
447		numToAdd = parseInt(numToAdd);
448		var newTypeCombinedTotal = numToAdd + existingTypeCombinedQuantity;
449		var newTotal = numToAdd + existingQuantity;
450		var errorThrown = false;
451
452		//fallback for some variables that should be set from php to get translated strings
453		if (typeof maxQuantOption == 'undefined') {
454			var maxQuantOption = 'Max quantity for this specific option is ';
455		}
456		if (typeof maxQuantType == 'undefined') {
457			var maxQuantType = 'Max quantity for this type is ';
458		}
459
460		// check if the new quantity + current quantity of a specific ticket type id go over the ticket type price max
461		if (newTotal > maxTicketTypePriceQuantity && maxTicketTypePriceQuantity > 0) {
462			alertify.alert("Notice", maxQuantOption + maxTicketTypePriceQuantity).set('modal', true).set('basic', false).pin();
463			errorThrown = true;
464		}
465
466		// check if the new total for a specific ticket TYPE (including this one and all others of this type) go over the type limit
467		if (!errorThrown && newTypeCombinedTotal > maxTicketTypeQuantity) {
468			alertify.alert("Notice", maxQuantType + maxTicketTypeQuantity).set('modal', true).set('basic', false).pin();
469			errorThrown = true;
470		}
471
472		// otherwise, update the quantity
473		if(!errorThrown) {
474
475			var currentValue = $('#Ticket' + row + 'Quantity').val();
476			if(!currentValue) {
477				currentValue = 0;
478			}
479			currentValue = parseInt(currentValue);
480
481			var newValue = numToAdd + currentValue;
482
483			$('#Ticket' + row + 'Quantity').val(newValue.toString());
484			calculate_order_total();
485
486		}
487	});
488
489	// when the "Add >" button is pressed
490	$('.merchandiseOptionsAddButton').on("click", function(e) {
491		e.preventDefault();
492
493		var type = "ticketType";
494		var maxTicketTypePriceQuantity = 0;
495		var ticketTypeId = $('#MerchandiseTypeSelect').val();
496		var ticketTypePriceId;
497		var maxTicketTypeQuantity = $("#OrderMerchandiseTypeTmpValues-" + ticketTypeId).attr("data-ticket-type-qty-max");
498
499		var quantitySelector = $('#MerchandiseType-' + ticketTypeId + '-quantitySelector');
500
501		if(typeof(quantitySelector.val()) == "undefined") {
502			ticketTypePriceId = $('#merchandiseTypePricesArea-'+ticketTypeId+' select:visible').val();
503			quantitySelector = $('#MerchandiseTypePrice-' + ticketTypePriceId + '-quantitySelector');
504			type = "ticketTypePrice";
505		}
506
507		row = quantitySelector.attr('data-ticket-type-row-key');
508		var existingTypeCombinedQuantity = getTotalQuantityOfSpecificType(ticketTypeId);
509		var existingQuantity = parseInt($('#Merchandise' + row + 'Quantity').val());
510		var numToAdd = quantitySelector.val();
511
512		if(type == 'ticketTypePrice') {
513			maxTicketTypePriceQuantity = parseInt($('#MerchandiseTypePrice-' + ticketTypePriceId + '-quantitySelector option:last-child').val());
514		}
515
516		numToAdd = parseInt(numToAdd);
517		var newTypeCombinedTotal = numToAdd + existingTypeCombinedQuantity;
518		var newTotal = numToAdd + existingQuantity;
519		var errorThrown = false;
520
521		//fallback for some variables that should be set from php to get translated strings
522		if (typeof maxQuantOption == 'undefined') {
523			var maxQuantOption = 'Max quantity for this specific option is ';
524		}
525		if (typeof maxQuantType == 'undefined') {
526			var maxQuantType = 'Max quantity for this type is ';
527		}
528
529		// check if the new quantity + current quantity of a specific ticket type id go over the ticket type price max
530		if (newTotal > maxTicketTypePriceQuantity && maxTicketTypePriceQuantity > 0) {
531			alertify.alert("Notice", maxQuantOption + maxTicketTypePriceQuantity).set('modal', true).set('basic', false).pin();
532			errorThrown = true;
533		}
534
535		// check if the new total for a specific ticket TYPE (including this one and all others of this type) go over the type limit
536		if (!errorThrown && newTypeCombinedTotal > maxTicketTypeQuantity) {
537			alertify.alert("Notice", maxQuantType + maxTicketTypeQuantity).set('modal', true).set('basic', false).pin();
538			errorThrown = true;
539		}
540
541		// otherwise, update the quantity
542		if(!errorThrown) {
543
544			var currentValue = $('#Ticket' + row + 'Quantity').val();
545			if(!currentValue) {
546				currentValue = 0;
547			}
548			currentValue = parseInt(currentValue);
549
550			var newValue = numToAdd + currentValue;
551
552			$('#Ticket' + row + 'Quantity').val(newValue.toString());
553			calculate_order_total();
554
555		}
556	});
557
558	// Shim <input required="true" /> behavior for Safari
559	//webshims.polyfill('forms');
560
561	$('#Contribution0Select').on("change", function(){
562		if ($(this).val() == 'other') {
563			$('#Contribution0Total').val('').show();
564			calculate_order_total();
565		} else if ($(this).val() == 'no') {
566			$('#Contribution0Total').val('0').hide();
567			calculate_order_total();
568		} else {
569			$('#Contribution0Total').val($(this).val()).hide();
570			calculate_order_total();
571		}
572	});
573
574    $('#promo_field_sm').on('keyup', function(e){
575        if (e.key === 'Enter' || e.keyCode === 13) {
576            e.preventDefault();
577            applyPromoCode('#promo_field_sm');
578        }
579    });
580
581    $('#promo_field').on('keyup', function(e){
582        if (e.key === 'Enter' || e.keyCode === 13) {
583            e.preventDefault();
584            applyPromoCode('#promo_field');
585        }
586    });
587
588    $('#promo_field_btm').on('keyup', function(e){
589        if (e.key === 'Enter' || e.keyCode === 13) {
590            e.preventDefault();
591            applyPromoCode('#promo_field_btm');
592        }
593    });
594
595
596
597	/**
598	 * ***********************************************************************
599	 * Moved this section from the bottom of the Orders::tickets view to here
600	 */
601
602    toggleShippingFields();
603
604    $('#OrderDeliveryMethod').on("change", function() {
605        //first, hide all existing verbiage
606        $('.option-verbiage').slideUp('fast');
607        //then show only the needed one
608        $('#option-' + $(this).val()).slideDown('fast');
609        //now recalculate the order total to calculate any fees
610        calculate_order_total();
611    });
612
613    $('.paymentToggle').on("click", function() {
614        switch ($(this).attr('id')) {
615            case 'OrderToggleCC':
616                switchPaymentMethod('ideal');
617                break;
618            case 'OrderToggleIDEAL':
619                switchPaymentMethod('creditCard');
620                break;
621        }
622    });
623
624    $('#OrderConfirmEmail').on("change", function() {
625        if ($('#OrderEmail').val() != $(this).val() && $('#OrderEmail').val().toUpperCase() != $(this).val().toUpperCase()) {
626            $(this).val('');
627            $(this).trigger("focus");
628            alertify.error('The email confirmation field must match the email field.');
629        }
630    });
631	/**
632	 * ***********************************************************************
633	 * END of section moved from bottom of tickets view  to here
634	 */
635
636	$("#ts-modal-btn-continue-shopping").on('click', function(event) {
637		window.parent.postMessage({
638			messageName: 'tsModalClose'
639		}, '*');
640	});
641
642	$("#ts-modal .ts-modal-reload-required .btn").on('click', function(event) {
643		const modalMessageData = {
644			ticketsUrl: window.location.origin + window.location.pathname
645		};
646		window.parent.postMessage({
647			messageName: 'tsReloadModal',
648			data: modalMessageData
649		}, '*');
650	});
651});
652
653
654
655function getTotalQuantityOfSpecificType(id) {
656	var total = 0;
657	$("[id ^='Ticket'][id $='Quantity'][data-ticket-type-id ='"+id+"']").each(function() {
658		if($(this).val() != '') {
659			total += parseInt($(this).val());
660		}
661	});
662	return total;
663}
664
665
666function disableSubmit(text) {
667	disableModalNextButton();
668	disableContinueShoppingButton();
669	if(!text) {
670		text = "Sending...";
671	}
672	$('#orderFormSubmitButton').addClass('disabledButton');
673	$('#orderFormSubmitButton').attr('disabled', true);
674	$('#orderFormSubmitButton').prop("disabled", true);
675	$('#orderFormSubmitButton').html(text);
676}
677
678
679function enableSubmit(text) {
680	ignoreLeaveAttempts(); // often already done, but adding here for those missed
681	if(!text) {
682		text = origSubmitButtonText;
683	}
684	$('#orderFormSubmitButton').removeClass('disabledButton');
685	$('#orderFormSubmitButton').prop('disabled', '');
686	$('#orderFormSubmitButton').html(text);
687}
688
689function disableModalNextButton() {
690	if ($('#ts-modal-btn-next-step').length) {
691		$('#ts-modal-btn-next-step').attr("disabled", true);
692		$('#ts-modal-btn-next-step').prop("disabled", true);
693	}
694}
695function enableModalNextButton() {
696	if ($('#ts-modal-btn-next-step').length) {
697		$('#ts-modal-btn-next-step').prop('disabled', '');
698	}
699}
700function disableContinueShoppingButton() {
701	if ($('#ts-modal-btn-continue-shopping').length) {
702		$('#ts-modal-btn-continue-shopping').attr("disabled", true);
703		$('#ts-modal-btn-continue-shopping').prop("disabled", true);
704	}
705}
706function enableContinueShoppingButton() {
707	if ($('#ts-modal-btn-continue-shopping').length) {
708		$('#ts-modal-btn-continue-shopping').prop('disabled', '');
709	}
710}
711
712function _updateLocalCartData() {
713	var cartId = _getCartId();
714	cartQuantities = {
715		ticketTypePrices: {},
716		credits: {},
717	};
718	document.querySelectorAll("select[name^='data[Ticket]'][name$='[quantity]']").forEach(function(select) {
719		var ticketTypePriceId = select.getAttribute('data-ticket-type-price-id');
720		var quantity = parseInt(select.value, 10);
721		if (quantity > 0) {
722			cartQuantities['ticketTypePrices'][ticketTypePriceId] = quantity;
723		}
724	});
725	document.querySelectorAll("select[name^='data[CreditProduct]'][name$='[quantity]']").forEach(function(select) {
726		var creditProductId = select.getAttribute('data-credit-product-id');
727		var quantity = parseInt(select.value, 10);
728		if (quantity > 0) {
729			cartQuantities['credits'][creditProductId] = quantity;
730		}
731	});
732	let eventIdElement = document.getElementById("OrderEventId");
733	if (!eventIdElement || !eventIdElement.value) {
734		return;
735	}
736	previousLocalCartData = localCartData;
737	localCartData = {
738		event_id: eventIdElement.value,
739		id: cartId,
740		quantities: cartQuantities,
741	};
742}
743
744function _hasLocalCartChanged() {
745	return JSON.stringify(previousLocalCartData) !== JSON.stringify(localCartData);
746}
747
748function _notifySavingCart() {
749	window.parent.postMessage({
750		messageName: 'tsSavingCart'
751	}, '*');
752}
753function _notifySavingCartComplete() {
754	window.parent.postMessage({
755		messageName: 'tsSavingCartComplete'
756	}, '*');
757}
758
759function isModal() {
760	return typeof thisWindowIsModal !== 'undefined' && thisWindowIsModal === true;
761}
762
763function _ajaxSaveCart() {
764	if (!isModal()) {
765		return;
766	}
767	_updateLocalCartData();
768	if (!_hasLocalCartChanged()) {
769		return;
770	}
771	_notifySavingCart();
772	$.ajax({
773		url: buildURL('/carts/ajax_save'),
774		type: 'POST',
775		contentType: 'application/json',
776		data: JSON.stringify(localCartData),
777		success: function(response) {
778			responseObject = JSON.parse(response);
779			if (responseObject.success) {
780				window.parent.postMessage({
781					messageName: 'tsModalCartSaveSuccess'
782				}, '*');
783			} else {
784				window.parent.postMessage({
785					messageName: 'tsModalCartSaveError'
786				}, '*');
787			}
788		},
789		error: function(xhr, status, error) {
790			window.parent.postMessage({
791				messageName: 'tsModalCartSaveError'
792			}, '*');
793		},
794		complete: function(xhr, status) {
795			_notifySavingCartComplete();
796		}
797	});
798}
799
800function applyPromoCode(promoFieldSelector, formId) {
801	$('#promo_field').val($(promoFieldSelector).val());
802	$('#promo_field_sm').val($(promoFieldSelector).val());
803	$('#promo_field_btm').val($(promoFieldSelector).val());
804    $('#real-promo-field').val($(promoFieldSelector).val());
805
806	if (typeof formId == 'undefined' || formId.length < 3) {
807		formId = '#orderForm';
808	}
809
810	calculate_order_total(formId);
811}
812
813
814// FUNCTION TO SHOW/HIDE THE STATE/PROVINCE/DISTRICT FIELDS BASED ON COUNTRY SELECTION
815function updateCountry() {
816	var selectedCountry = $('#OrderCountry').val();
817	switch(selectedCountry){
818		case 'US':
819			$('.orderProvinceArea select').prop('disabled', 'disabled');
820			$('.orderProvinceArea').hide();
821			$('.orderMxDistrictArea select').prop('disabled', 'disabled');
822			$('.orderMxDistrictArea').hide();
823			$('.orderAustralianStateArea select').prop('disabled', 'disabled');
824			$('.orderAustralianStateArea').hide();
825			$('.orderUSSTateArea select').removeAttr('disabled');
826			$('.orderUSSTateArea').show();
827			break;
828		case 'CA':
829			$('.orderUSSTateArea select').prop('disabled', 'disabled');
830			$('.orderUSSTateArea').hide();
831			$('.orderMxDistrictArea select').prop('disabled', 'disabled');
832			$('.orderMxDistrictArea').hide();
833			$('.orderAustralianStateArea select').prop('disabled', 'disabled');
834			$('.orderAustralianStateArea').hide();
835			$('.orderProvinceArea select').removeAttr('disabled');
836			$('.orderProvinceArea').show();
837			break;
838		case 'MX':
839			$('.orderUSSTateArea select').prop('disabled', 'disabled');
840			$('.orderUSSTateArea').hide();
841			$('.orderProvinceArea select').prop('disabled', 'disabled');
842			$('.orderProvinceArea').hide();
843			$('.orderAustralianStateArea select').prop('disabled', 'disabled');
844			$('.orderAustralianStateArea').hide();
845			$('.orderMxDistrictArea select').removeAttr('disabled');
846			$('.orderMxDistrictArea').show();
847			break;
848		case 'AU':
849			$('.orderUSSTateArea select').prop('disabled', 'disabled');
850			$('.orderUSSTateArea').hide();
851			$('.orderMxDistrictArea select').prop('disabled', 'disabled');
852			$('.orderMxDistrictArea').hide();
853			$('.orderProvinceArea select').prop('disabled', 'disabled');
854			$('.orderProvinceArea').hide();
855			$('.orderAustralianStateArea select').removeAttr('disabled');
856			$('.orderAustralianStateArea').show();
857			break;
858		default:
859			$('.orderUSSTateArea select').prop('disabled', 'disabled');
860			$('.orderUSSTateArea').hide();
861			$('.orderProvinceArea select').prop('disabled', 'disabled');
862			$('.orderProvinceArea').hide();
863			$('.orderAustralianStateArea select').prop('disabled', 'disabled');
864			$('.orderAustralianStateArea').hide();
865			$('.orderMxDistrictArea select').prop('disabled', 'disabled');
866			$('.orderMxDistrictArea').hide();
867			break;
868	}
869}
870
871
872// FUNCTION TO SHOW/HIDE THE SEPARATE SHIPPING STATE/PROVINCE/DISTRICT FIELDS BASED ON COUNTRY SELECTION
873function updateCountryShipping() {
874	var selectedCountry = $('#OrderCountryShipping').val();
875	switch(selectedCountry){
876		case 'US':
877			$('.orderProvinceAreaShipping select').prop('disabled', 'disabled');
878			$('.orderProvinceAreaShipping').hide();
879			$('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled');
880			$('.orderMxDistrictAreaShipping').hide();
881			$('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled');
882			$('.orderAustralianStateAreaShipping').hide();
883			$('.orderUSSTateAreaShipping select').removeAttr('disabled');
884			$('.orderUSSTateAreaShipping').show();
885			break;
886		case 'CA':
887			$('.orderUSSTateAreaShipping select').prop('disabled', 'disabled');
888			$('.orderUSSTateAreaShipping').hide();
889			$('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled');
890			$('.orderMxDistrictAreaShipping').hide();
891			$('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled');
892			$('.orderAustralianStateAreaShipping').hide();
893			$('.orderProvinceAreaShipping select').removeAttr('disabled');
894			$('.orderProvinceAreaShipping').show();
895			break;
896		case 'MX':
897			$('.orderUSSTateAreaShipping select').prop('disabled', 'disabled');
898			$('.orderUSSTateAreaShipping').hide();
899			$('.orderProvinceAreaShipping select').prop('disabled', 'disabled');
900			$('.orderProvinceAreaShipping').hide();
901			$('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled');
902			$('.orderAustralianStateAreaShipping').hide();
903			$('.orderMxDistrictAreaShipping select').removeAttr('disabled');
904			$('.orderMxDistrictAreaShipping').show();
905			break;
906		case 'AU':
907			$('.orderUSSTateAreaShipping select').prop('disabled', 'disabled');
908			$('.orderUSSTateAreaShipping').hide();
909			$('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled');
910			$('.orderMxDistrictAreaShipping').hide();
911			$('.orderProvinceAreaShipping select').prop('disabled', 'disabled');
912			$('.orderProvinceAreaShipping').hide();
913			$('.orderAustralianStateAreaShipping select').removeAttr('disabled');
914			$('.orderAustralianStateAreaShipping').show();
915			break;
916		default:
917			$('.orderUSSTateAreaShipping select').prop('disabled', 'disabled');
918			$('.orderUSSTateAreaShipping').hide();
919			$('.orderProvinceAreaShipping select').prop('disabled', 'disabled');
920			$('.orderProvinceAreaShipping').hide();
921			$('.orderAustralianStateAreaShipping select').prop('disabled', 'disabled');
922			$('.orderAustralianStateAreaShipping').hide();
923			$('.orderMxDistrictAreaShipping select').prop('disabled', 'disabled');
924			$('.orderMxDistrictAreaShipping').hide();
925			break;
926	}
927}
928
929function toggleShippingFields() {
930	if ($('#OrderUseBilling').prop('checked')) {
931		$('.shipping-fields').slideUp('fast');
932		$('.shipping-fields input').each(function(){
933			$(this).prop('disabled', true);
934		});
935		$('.shipping-fields select').each(function(){
936			$(this).prop('disabled', true);
937		});
938	} else {
939		$('.shipping-fields input').each(function(){
940			$(this).removeAttr('disabled');
941		});
942		$('.shipping-fields select').each(function(){
943			$(this).removeAttr('disabled');
944		});
945		$('.shipping-fields').slideDown('fast');
946		updateCountryShipping();
947	}
948}
949
950
951/**
952 * need to check for ticket insurance and run that before calculation.
953 */
954function calculate_order_total(formId){
955	hasRunCalculate = true;
956	// checking here in case they select tickets before the DOM is complete and loaded.
957	ticketInsuranceJsLoaded = (typeof updateInsuranceQuote === "function");
958	if(ticketInsuranceJsLoaded){
959		// check for insurance quote before order calculation.
960		updateInsuranceQuote(actual_calculate_order_total);
961	} else {
962		// just run the order calculation
963		actual_calculate_order_total(formId);
964	}
965}
966
967/**
968 * Ajax call to calculate the order and return json results of all the parts making up the total
969 * @return {[type]} [description]
970 */
971var numCalculations = 0;
972function actual_calculate_order_total(formId) {
973	numCalculations++;
974	var thisCalculationNum = numCalculations;
975
976	disableSubmit(origSubmitButtonText);
977
978	// check to make sure any linked ticket type quantities are fulfilled
979	updateLinkedTypes();
980
981	// check to make sure allowed quantities of multi-prices are selected
982	// example - if the ticket type allows only 1 per order, but there are 3 price options, they can't be allowed to select more than one
983
984	var maxSelected = 0;
985	var overTheLimit = false;
986	var overRemaining = false;
987	var typeQtySelected = 0;
988	var selectedValue = 0;
989	var waitListOnly = $('#ticket-types-content').hasClass('wait-list-only');
990
991	$(".ticket-type-prices").each(function() {
992		maxSelected     = parseInt($(this).attr('data-max-per-order'));
993		numRemaining    = parseInt($(this).attr('data-num-remaining'));
994		typeQtySelected = 0;
995		var fieldName = '';$(this).attr('name');
996
997		$(this).find('.ticketOptions div select[name$="[quantity]"]').each(function(i, value) {
998
999			var selectedValue = parseInt($(value).val());
1000
1001			if(isNaN(selectedValue)) {
1002				selectedValue = 0;
1003			}
1004			typeQtySelected += selectedValue;
1005
1006			if(maxSelected != 0 && typeQtySelected > maxSelected) {
1007				overTheLimit = true;
1008				$(value).prop('selectedIndex', 0);
1009			}
1010			if(typeQtySelected > numRemaining && !waitListOnly) {
1011				overRemaining = true;
1012				$(value).prop('selectedIndex', 0);
1013			}
1014		});
1015
1016		//fallback if translated strings are missing
1017		if (typeof onlyText == 'undefined') {
1018			var onlyText = "Only ";
1019			var maxText = "Max ";
1020			var remainText = " of this type remaining.";
1021			var perOrderText = " of this type per order.";
1022		}
1023
1024		if (overRemaining) {
1025			alertify.alert("Notice", onlyText + numRemaining + remainText).set('modal', true).set('basic', false).pin();
1026			return false;
1027		} else if (overTheLimit) {
1028			alertify.alert("Notice", maxText + maxSelected + perOrderText).set('modal', true).set('basic', false).pin();
1029			return false;
1030		}
1031	});
1032
1033
1034	disableModalNextButton();
1035	disableContinueShoppingButton();
1036	$(".orderPriceDetails").slideDown(200);
1037	$(".orderPriceDetails").fadeTo('fast', 0.4);
1038	$(".orderPriceDetails .ajax_loader").fadeIn('fast');
1039	$(".orderSummary").fadeTo('fast', 0.4);
1040	$('.promo_value').val($('#real-promo-field').val());
1041	if($('.promo_value').val() == 'Promo Code') {
1042		$('.promo_value').val('');
1043	}
1044
1045	// repeats through each quantity-select box
1046	var ticketTypeNum = null;
1047	var fieldName = null;
1048	var ticketsPer = null;
1049	var quantity;
1050	var tmpVal = "";
1051	var ticketTypeName;
1052	var ticketTypePriceId;
1053	var atLeastOneTicketQuantity = false;
1054	var quantityPrices = [];
1055	var totalQuantity = 0;
1056
1057	$('[name$="[quantity]"]').each(function() {
1058
1059		fieldName = $(this).attr('name');
1060
1061		ticketTypeNum = fieldName.match(/\d+/);
1062
1063		// ticketTypeName = $(this).siblings('[name$="[ticket_type_name]"]').val();
1064		// ticketTypePriceId = $(this).siblings('[name$="[ticket_type_price_id]"]').val();
1065		ticketTypeName = $('#Ticket' + ticketTypeNum + 'TicketTypeName').val();
1066		ticketTypePriceId = $('#Ticket' + ticketTypeNum + 'TicketTypePriceId').val();
1067
1068		// TODO: this is just getting the first ticket type price id
1069
1070		if(ticketTypeName == undefined) { // for use in the multi-tickets
1071			ticketTypeName = $(this).parent().siblings('[name$="[ticket_type_name]"]').val();
1072			ticketTypePriceId = $(this).parent().siblings('[name$="[ticket_type_price_id]"]').val();
1073		}
1074
1075		ticketsPer = $(this).attr('data-tickets-per'); // number of tickets to generate per "1" selected (ie "")
1076		quantity = $(this).val();
1077		totalQuantity += parseInt(quantity);
1078
1079		// saves if there's been at least one quantity
1080		if ($(this).val() > 0) {
1081			if($('#Ticket' + ticketTypeNum + 'ProductType').val() == 'ticket'){
1082				atLeastOneTicketQuantity = true;
1083			}
1084			// if Ticket Insurance Is enabled.
1085			if($('#OrderTicketInsuranceStatus').val() == 1){
1086				 // adding up for the policy
1087                for(i = 0; i < quantity; i++){
1088                    if($('#Ticket' + ticketTypeNum + 'PriceEach').val() != "0.00" && $('#Ticket' + ticketTypeNum + 'ProductType').val() == 'ticket'){
1089                        quantityPrices.push($('#Ticket' + ticketTypeNum + 'PriceEach').val());
1090                    }
1091                }
1092			}
1093		}
1094		// get/store previous ticket names for this ticket type/price and removes the name rows
1095		var values = [];
1096		$('[name^="data[TicketName]['+ticketTypePriceId + '"]').each(function() {
1097			if(values[ticketTypePriceId] == undefined) {
1098				values[ticketTypePriceId] = [];
1099			}
1100
1101			// if (ticketsPer > 1) {
1102			values[ticketTypePriceId].push($(this).val());
1103			$(this).parent().remove();
1104		});
1105
1106		var input_required;
1107
1108	});
1109
1110	if (typeof formId == 'undefined' || formId.length < 3) {
1111		if (typeof gFormId == 'undefined') {
1112			formId = '#' + $('form').attr('id');
1113		} else {
1114			formId = '#' + gFormId;
1115		}
1116	}
1117
1118	//Handle the timer_start_time field
1119
1120	//get the potential local timer start time (we need the time from the user's machine to get an accurate countdown)
1121	startTime = new Date();
1122	//convert it to UTC
1123	startTime = new Date(startTime.getTime() + startTime.getTimezoneOffset() * 60000);
1124	//check whether the field already exists or needs to be created
1125	if (!$('input[name="timer_start_time"]').length) {
1126		//put the date in a hidden input appended to the form
1127		$(formId).append('<input type="hidden" name="timer_start_time" value="' + startTime + '" />');
1128	} else {
1129		//already exists, insert updated value
1130		$('input[name="timer_start_time"]').val(startTime);
1131	}
1132
1133	var fieldsToSubmit = getOrderFormDataWithoutUnmodifiedZeroQuantityTickets(formId);
1134
1135	var overlimit = false;
1136
1137	$.ajax({
1138		type: "POST",
1139		url: buildURL('/orders/calculate_price'),
1140		data: fieldsToSubmit,
1141		dataType: "json",
1142		success: function(data) {
1143			if (data['Timer'] != false) {
1144				if ($('#registration-timer').css('display') == 'none') {
1145					// start the timer
1146					timerCountdown(data['Timer'], 'registration-timer');
1147					// $('#registration-timer').show();
1148					$('#registration-timer').css('display', 'flex');
1149				}
1150			} else {
1151				//timer is false, this could either mean there was never a timer - no timer even enabled,
1152				//OR it could mean there was a timer previously but now it needs to be turned off,
1153				//so we need to check to see if a timer is in play, and deactivate it if so
1154				if ($('#registration-timer').css('display') == 'flex') {
1155					clearInterval(interval);
1156					$('#registration-timer').hide();
1157					$('#registration-timer .timer-minutes').text('');
1158				}
1159			}
1160
1161			if (data['overLimit'] != false) {
1162				alertify.alert(qtyNotAvailMsg).set('title', qtyNotAvailTitle);
1163				$.each(data['overLimit'], function(index, value){
1164					overlimit = true;
1165					if (value <= 0) {
1166						$('select[data-ticket-type-price-id="' + index + '"]').val('');
1167						$('*[data-ticket-type-price-id="' + index + '"]').val('');
1168					} else {
1169						$('select[data-ticket-type-price-id="' + index + '"]').val(value);
1170						$('*[data-ticket-type-price-id="' + index + '"]').val(value);
1171					}
1172
1173					// if was overlimit and adjusted, change the drop down to not allow another attempt
1174					// to overbook.
1175					$('*[data-ticket-type-price-id="' + index + '"] option').filter(function(){
1176						return parseInt(this.value) > value;
1177					}).remove();
1178				});
1179			}
1180
1181			// Leave exchange prefills intact; removing #ticket-<id> cleared required hidden inputs.
1182			if (data['purchase_location'] == 'online' && data['orderTicketLimitPassed'] != false) {
1183				var qtyTicketLimitTitle = 'Order Limit Exceeded';
1184				// var qtyTicketLimitMsg = 'Orders on this event are limited to ' + data['orderTicketLimit'] + ' items. Please adjust your quantities.';
1185				var qtyTicketLimitMsg = 'You\'ve exceeded the quantity limits on this event. Please adjust your quantities and try again.'; //CZ - temp generic message for Kokefest launch April 2022, TODO: allow client to customize failure message
1186				alertify.alert(qtyTicketLimitTitle, qtyTicketLimitMsg).setting({
1187					'message': qtyTicketLimitMsg,
1188					'modal': true,
1189					'basic': false,
1190					'closable': true,
1191					'closableByDimmer': false})
1192					.set({
1193						onok: function(closeEvent) { return true; }, // i think it didn't jump down before having to add this
1194					});
1195			}
1196
1197			if (typeof data['registrationClosed'] != 'undefined' && data['registrationClosed'] == true) {
1198				alertify.alert(qtyNotAvailMsg).set('title', qtyNotAvailTitle);
1199				$.each(data['registrationClosed'], function(index, value){
1200					$('input[name$="[activity_instance_id]"], input[value="'+value+'"]')
1201						.closest('.ticketOptions')
1202						.find('select[name$="[quantity]"]').val('');
1203				});
1204			}
1205
1206			if(thisCalculationNum == numCalculations) {
1207				// does not have a summary or questionnaires on the iframe view
1208				if ($('#OrderSource').val() != 'iframe'){
1209					if ((data.giftCardStatus === 'expired' || data.giftCardStatus === 'inactive') && data.giftCardMessage) {
1210						alertify.alert(data.giftCardError, data.giftCardMessage).set('modal', true).set('basic', false).pin();
1211					}
1212					updateQuestionnaires();
1213					updateStripeMetadataFields(data, formId);
1214					updateSummary(data, formId);
1215					if($('#OrderPaymentIntentId').val()){
1216						// nothing needed
1217					} else {
1218						enableSubmit(origSubmitButtonText);
1219					}
1220				}
1221			}
1222			if (orderTotalQuantity() <= 0) {
1223				disableModalNextButton();
1224				setHasModalSelection(false);
1225			} else {
1226				enableModalNextButton();
1227				setHasModalSelection(true);
1228			}
1229			enableContinueShoppingButton();
1230
1231			if (typeof updateDripCart === 'function') {
1232				updateDripCart(); // defined in main.js
1233			}
1234			if (isModal()) {
1235				_ajaxSaveCart();
1236				const modalMessageData = {
1237					windowId: getWindowId(),
1238					totalQuantity: totalQuantity,
1239					quantitiesHash: fnv1aHash(JSON.stringify(localCartData)),
1240					quantitiesUpdated: Date.now(),
1241					ticketsUrl: window.location.origin + window.location.pathname
1242				};
1243				window.parent.postMessage({
1244					messageName: 'tsUpdateModalCart',
1245					data: modalMessageData
1246				}, '*');
1247			}
1248		},
1249		error: function (errorMessage) {
1250			return false;
1251		}
1252	});
1253	if(overlimit){
1254		calculate_order_total();
1255	}
1256}
1257
1258function fnv1aHash(str) {
1259	let hash = 0x811c9dc5; // FNV offset basis
1260	for (let i = 0; i < str.length; i++) {
1261		hash ^= str.charCodeAt(i);
1262		hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
1263	}
1264	return (hash >>> 0).toString(16); // Ensure unsigned 32-bit output
1265}
1266
1267function getWindowId() {
1268	const urlParams = new URLSearchParams(window.location.search);
1269	if (urlParams.has('wid')) {
1270	  return urlParams.get('wid');
1271	}
1272	return "";
1273}
1274
1275function updateStripeMetadataFields(data, formId) {
1276	let quantityOfTicketProducts = data['totalTicketProducts'];
1277	let calculatedSubtotal = data['subTotal'];
1278	let calculatedLETTax = getLETTaxFromCustomFeesOrderData(data['CustomFeesOrders']);
1279	let calculatedSurchargeFees = getSurchargeFeeFromCustomFeesOrderData(data['CustomFeesOrders']);
1280	let calculatedAdminCharges = data['admin_charges'];
1281	let calculateDiscountAmt = data['promoCodeAmount'];
1282	$('#OrderQtyTickets').val(quantityOfTicketProducts);
1283	$('#OrderSubtotal').val(calculatedSubtotal);
1284	$('#OrderLetTax').val(calculatedLETTax);
1285	$('#OrderTotalSurcharge').val(calculatedSurchargeFees);
1286	$('#OrderAdminCharges').val(calculatedAdminCharges);
1287	$('#OrderDiscountAmt').val(calculateDiscountAmt);
1288}
1289
1290
1291function getSurchargeFeeFromCustomFeesOrderData(data) {
1292	let returnValue = 0;
1293
1294	if (!Array.isArray(data)) {
1295		return returnValue;
1296	}
1297
1298	$.each(data, function(key, value) {
1299		switch(value['custom_fee_name'].toLowerCase()) {
1300			case 'surcharge fee':
1301			case 'surcharge':
1302				returnValue += parseFloat(value['actual_fee_applied']);
1303				break;
1304		}
1305	});
1306	return returnValue;
1307}
1308
1309function getLETTaxFromCustomFeesOrderData(data) {
1310	let returnValue = 0;
1311
1312	if (!Array.isArray(data)) {
1313		return returnValue;
1314	}
1315
1316	$.each(data, function(key, value) {
1317		switch(value['custom_fee_name'].toLowerCase()) {
1318			case 'let tax':
1319			case 'let':
1320				returnValue += parseFloat(value['actual_fee_applied']);
1321				break;
1322		}
1323	});
1324	return returnValue;
1325}
1326
1327
1328function setHasModalSelection(hasSelection) {
1329	tsModalHasSelection = hasSelection;
1330	if (hasSelection) {
1331		window.parent.postMessage({
1332			messageName: 'modalHasSelection'
1333		}, '*');
1334	} else {
1335		window.parent.postMessage({
1336			messageName: 'modalNothingSelected'
1337		}, '*');
1338		evnt = new CustomEvent('modalNothingSelected');
1339		document.dispatchEvent(evnt);
1340	}
1341}
1342
1343function handleModalOrderComplete() {
1344	const modalMessageData = {
1345		ticketsUrl: window.location.origin + window.location.pathname,
1346	};
1347	window.parent.postMessage(
1348		{
1349			messageName: "tsModalOrderComplete",
1350			data: modalMessageData,
1351		},
1352		"*",
1353	);
1354	setHasModalSelection(false);
1355}
1356
1357function updateLinkedTypes() {
1358	// for each ticket type:
1359	// if master required, check how many in cart, and limit dropdown accordingly
1360	$("div[id$='Quantityreq']").remove();
1361
1362	// standard display
1363	$('select[name$="[quantity]"]').each(function() {
1364		// admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end for admin.
1365		if ($(this).attr('data-linked-to')) {
1366			// split the json array
1367			// get the total max allowed qty
1368			var linkto = $(this).data('linked-to');
1369			var linkmessage = $(this).data('link-message');
1370			var ticketsper = $(this).data('tickets-per');
1371			var currentMasterQty = 0;
1372			var allowedQty = 0;
1373			var origMax = 0;
1374			var origMin = 0;
1375			if(linkto != '') {
1376				if ($(this).attr('data-link-orig-max')) {
1377					origMax = $(this).attr('data-link-orig-max');
1378					origMin = $(this).attr('data-link-orig-min');
1379				} else {
1380					$(this).attr('data-link-orig-max',$(this).children('option:last').val());
1381					origMax = $(this).attr('data-link-orig-max');
1382					$(this).attr('data-link-orig-min',$(this).children('option:eq(1)').val());
1383					origMin = $(this).attr('data-link-orig-min');
1384				}
1385				currentval = '';
1386				currentval = $(this).val();
1387				$(this).val('');
1388				currentMasterQty = 0;
1389				$.each(linkto, function(key,value){
1390					$('[data-ticket-type-id='+ key +']').each(function() {
1391						currentMasterQty += Number($(this).val()) * value;
1392					});
1393				})
1394				allowedQty = Math.min(currentMasterQty, origMax);
1395				if (allowedQty < $(this).children('option:last').val()) {
1396					$(this).children().filter(function() {
1397						return this.value > allowedQty;
1398					}).remove();
1399				} else if (allowedQty > $(this).children('option:last').val()) {
1400					var i=0;
1401					for (i=Number($(this).children('option:last').val()) + 1; i < allowedQty +1; i++) {
1402						if (i>=origMin) {
1403							if (ticketsper > 1) {
1404								$(this).append($('<option>
1404', {value: i, text: i + ' (' + i * ticketsper + ' tickets)'}));
1405							} else {
1406								$(this).append($('<option>', {value:i, text:i}));
1407							}
1408						}
1409					}
1410				}
1411
1412				if(currentval <= allowedQty) $(this).val(currentval);
1413
1414				if($(this).children('option:last').val() == '' || $(this).children('option:last').val() == '0') {
1415					$(this).hide().after("<div id='"+$(this).attr('id')+"req' class='sold-out-text'>"+linkmessage+"</div>");
1416				} else {
1417					$(this).show();
1418					$("#"+$(this).attr('id')+"req").hide();
1419				}
1420
1421
1422			}
1423		}
1424	});
1425
1426	// modern theme
1427	$('input[name$="[quantity]"]').each(function() {
1428		// admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end for admin.
1429		if ($(this).attr('data-linked-to')) {
1430			// split the json array
1431			// get the total max allowed qty
1432			var linkto = $(this).data('linked-to');
1433			var linkmessage = $(this).data('link-message');
1434			var ticketsper = $(this).data('tickets-per');
1435			var currentMasterQty = 0;
1436			var allowedQty = 0;
1437			var origMax = 0;
1438			var origMin = 0;
1439			if(linkto != '') {
1440
1441				origMin = $(this).attr('min');
1442				origMax = $(this).attr('max');
1443
1444				currentval = '';
1445				currentval = $(this).val();
1446
1447				$(this).val('');
1448				currentMasterQty = 0;
1449				$.each(linkto, function(key,value){
1450					$('[data-ticket-type-id='+ key +']').each(function() {
1451						currentMasterQty += $(this).val() * value;
1452					});
1453				});
1454
1455				allowedQty = currentMasterQty;
1456				$(this).attr('max', allowedQty);
1457
1458				if(currentval <= allowedQty){
1459					$(this).val(currentval);
1460				} else {
1461					$(this).val(allowedQty);
1462				}
1463
1464				if($(this).attr('max') == 0) {
1465					$(this).parent().parent().find('.btn-input-subtract').hide();
1466					$(this).parent().parent().find('.btn-input-add').hide();
1467					$(this).hide().after("<p id='"+$(this).attr('id')+"req' class='ticket-sold-out-text'>" + linkmessage + "</p>");
1468				} else {
1469					$(this).show();
1470					$(this).parent().parent().find('.btn-input-subtract').show();
1471					$(this).parent().parent().find('.btn-input-add').show();
1472					$("#"+$(this).attr('id')+"req").remove();
1473
1474					if($(this).val() < $(this).attr('max')){
1475						$(this).parent().parent().find('.btn-input-add').removeClass('disabled');
1476					}
1477				}
1478
1479
1480			}
1481		}
1482	});
1483
1484	// compact display
1485	$('select[name*="[quantity-general"]').each(function() {
1486		// admin order tickets page doesn't have the attributes, and limits shouldn't be applied front-end.
1487		if ($(this).attr('data-linked-to')) {
1488			var linkto = $(this).data('linked-to');
1489			var linkmessage = $(this).data('link-message');
1490			var ticketsper = $(this).data('tickets-per');
1491			var currentMasterQty = 0;
1492			var allowedQty = 0;
1493			var origMax = 0;
1494			var origMin = 0;
1495			if(linkto != '') {
1496				if ($(this).attr('data-link-orig-max')) {
1497					origMax = $(this).attr('data-link-orig-max');
1498					origMin = $(this).attr('data-link-orig-min');
1499				} else {
1500					$(this).attr('data-link-orig-max',$(this).children('option:last').val());
1501					origMax = $(this).attr('data-link-orig-max');
1502					$(this).attr('data-link-orig-min',$(this).children('option:first').val());
1503					origMin = $(this).attr('data-link-orig-min');
1504				}
1505				$.each(linkto, function(key,value){
1506					$('[data-ticket-type-id='+ key +']').filter('input[type=hidden]').each(function() {
1507						currentMasterQty += Number($(this).val()) * value;
1508					});
1509				})
1510				allowedQty = Math.min(currentMasterQty, origMax);
1511				if ((allowedQty < $(this).children('option:last').val())) {
1512					$(this).children().filter(function() {
1513						return this.value > allowedQty;
1514					}).remove();
1515				} else if ($(this).children().length == 0 ||  allowedQty > $(this).children('option:last').val()) {
1516					var i=0;
1517					var start=0;
1518					if ($(this).children().length != 0) start=Number($(this).children('option:last').val());
1519					for (i=start + 1; i < allowedQty +1; i++) {
1520						if (i>=origMin) {
1521							if (ticketsper > 1) {
1522								$(this).append($('<option>', {value: i, text: i + ' (' + i * ticketsper + ' tickets)'}));
1523							} else {
1524								$(this).append($('<option>', {value:i, text:i}));
1525							}
1526						}
1527					}
1528				}
1529
1530				//remove from cart if necessary
1531				var numInCart = 0;
1532				var thisTypeId = $(this).attr('data-ticket-type-id');
1533				$('[data-ticket-type-id='+ thisTypeId +']').filter('input[type=hidden]').each(function() {
1534					numInCart += Number($(this).val());
1535				});
1536				if (numInCart > allowedQty) {
1537					$('[data-ticket-type-id='+ thisTypeId +']').filter('input[type=hidden]').val(0);
1538				}
1539
1540			}
1541		}
1542	})
1543}
1544
1545function removeThisTicketType(ticketTypeRow) {
1546	$('[name="data[Ticket][' + ticketTypeRow + '][quantity]"]').val(0);
1547	calculate_order_total();
1548}
1549
1550
1551/**
1552 * Builds the Summary area after calculating the totals...etc
1553 * @return {[type]} [description]
1554 */
1555function updateSummary(data, formId) {
1556	var seattext = data.textStrings.seattext;
1557	var donation = data.textStrings.donation;
1558	var noitems = data.textStrings.noitems
1559	var content = "";
1560	var typeName = '';
1561	var activityInfo = '';
1562	var aittId = '';
1563	var quantity = 0;
1564	var row = 0;
1565	var bundledFees;
1566	if (typeof window.currencySymbol === 'undefined') {
1567		window.currencySymbol = "$";
1568	}
1569	if (typeof formId == 'undefined') {
1570		formId = '#orderForm';
1571	}
1572
1573    //clear out any failed promo codes to prevent the wrong order summary data
1574    if (data.promoCodeMessage == 'Invalid promo code') {
1575        $('#real-promo-field').val('');
1576        $('#promo_field').val('');
1577        $('#promo_field_sm').val('');
1578        $('#promo_field_btm').val('');
1579    }
1580
1581	//pre-populate the cash tendered total fields on the day of event sales page
1582	$('#CashTenderedTotal').val(data.total);
1583	$('#CashTendered').val(data.total);
1584	$('#ChangeDue').val(parseFloat(0).toFixed(2));
1585
1586	var exchange = false;
1587	if($('#OrderPurchaseLocation').val() == 'exchange'){
1588		exchange = true;
1589	}
1590
1591	const exchangeCreditDisplay = data.exchange_credit_display !== undefined ? data.exchange_credit_display : data.exchange_credit;
1592
1593	let purchasedTicketMap = {};
1594	if (exchange && data.Ticket && Array.isArray(data.Ticket)) {
1595		data.Ticket.forEach(function(ticket) {
1596			if (ticket.Purchased && ticket.Purchased.Ticket && ticket.id) {
1597				purchasedTicketMap[ticket.id] = ticket.Purchased.Ticket;
1598			}
1599		});
1600	}
1601
1602	// list the ticket types and their quantities (Ticket Type List View)
1603	$('.ticketOptions').each(function() {
1604		ttPriceId = $(this).find('[name$="ticket_type_price_id]"]').val();
1605		aittId = $(this).find('[name$="activity_instance_ticket_type_id]"]').val();
1606
1607		//if exchange, replace the activity instance ticket type id, with ticket id
1608		ticketId = $(this).find('[name$="[Purchased][Ticket][id]"]').val();
1609
1610		var removeIndex = ttPriceId;
1611		if(exchange === true){
1612			removeIndex = ticketId;
1613		}
1614
1615		timeConflict = '';
1616		if (data.time_conflicts.indexOf(aittId) !== -1) {
1617			timeConflict = '<span style="color:red;">*</span> ';
1618		}
1619		typeName = '';
1620		if (data.Ticket && Array.isArray(data.Ticket)) {
1621			const ttPriceId = $(this).find('[name$="ticket_type_price_id]"]').val();
1622
1623			const match = data.Ticket.find(function(t) {
1624				return t.ticket_type_price_id === ttPriceId;
1625			});
1626
1627			if (match && match.ticket_type_name) {
1628				typeName = match.ticket_type_name;
1629			}
1630		}
1631		if (!typeName) {
1632			typeName = $(this).find('[name$="ticket_type_name]"]').val(); // fallback
1633		}
1634		quantity = $(this).find('[name$="quantity]"]').val();
1635		if($(this).find('[name$="activity_name]"]').val() === undefined || $(this).find('[name$="activity_name]"]').val() === ''){
1636			activityInfo = '';
1637		} else {
1638			activityInfo = $(this).find('[name$="activity_name]"]').val() + '<br><i class="fa-regular fa-calendar-days"></i>&nbsp;' + $(this).find('[name$="activity_instance_date_time]"]').val();
1639		}
1640
1641		if(exchange === true){
1642			aittId = ticketId;
1643		} else {
1644			aittId = $(this).find('[name$="activity_instance_ticket_type_id]"]').val();
1645		}
1646
1647		priceEach = $(this).find('[name$="price_each]"]').val();
1648		if (priceEach) {
1649			priceEach = priceEach.replace(',','');
1650			selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2);
1651		} else {
1652			selectedQuantityPrice = '0.00';
1653		}
1654
1655		var seatLabels = $(this).find('[name$="seat_labels]"]').val();
1656		if (seatLabels) {
1657			seatLabels = seatLabels.split(',');
1658			var seatLabelArgs = '[';
1659			for (var i = 0; i < seatLabels.length; i++) {
1660				//add each seat label, trimmed and escaped
1661				seatLabelArgs += "'" + seatLabels[i].trim().replace(/'/g, "\\'") + "',";
1662			}
1663			seatLabelArgs = seatLabelArgs.slice(0, -1);
1664			seatLabelArgs += ']';
1665		}
1666
1667		if(quantity && parseInt(quantity) > 0) {
1668			switch(tsTheme) {
1669				case 'Modern':
1670					if (data['purchase_location'] == 'exchange') {
1671						const purchasedTicketId = $(this).find('[name*="[Purchased][Ticket][id]"]').val();
1672						let purchasedTicket = purchasedTicketMap[purchasedTicketId];
1673
1674						if (!purchasedTicket) {
1675							break;
1676						}
1677
1678						let purchasedTicketName = purchasedTicket['ticket_type_name'] || '';
1679						let purchasedTicketPriceName = purchasedTicket['ticket_type_price_name'] || '';
1680
1681						// Determine display name for purchased ticket
1682						let displayName = purchasedTicketName;
1683						if (purchasedTicketPriceName != 'default' && purchasedTicketPriceName != 'standard') {
1684							displayName += ' ' + purchasedTicketPriceName;
1685						}
1686
1687						// Exchange to row
1688						content += `<div class="table-row"><div class="table-cell del"><a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), '${removeIndex}', '${formId}', '${aittId}');"><i class="fa fa-times" aria-hidden="true"></i></a></div><div class="table-cell type"><strong>Exchange to:</strong><br>${typeName}`;
1689						if (activityInfo != '') {
1690							content += '<br><div class="details">' + timeConflict + activityInfo + '</div>';
1691						}
1692						content += '</div><div class="table-cell price">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div></div>';
1693
1694						// From row
1695						content += '<div class="table-row" style="padding-top:0;"><div class="table-cell del" style="padding-right:26.8px;"></div><div class="table-cell type"><strong>From:</strong><br>' + displayName;
1696						// Add activity information if it exists in the purchased ticket
1697						if (purchasedTicket['instance_label_name'] && purchasedTicket['instance_label_name'] !== '') {
1698							content += '<br><div class="details">' + purchasedTicket['instance_label_name'] + '</div>';
1699						}
1700						if (purchasedTicket['instance_label'] && purchasedTicket['instance_label'] !== '') {
1701							// Extract the date/time part from the instance_label (everything after the last dash)
1702							let instanceLabel = purchasedTicket['instance_label'];
1703							let lastDashIndex = instanceLabel.lastIndexOf(' - ');
1704							if (lastDashIndex !== -1) {
1705								let dateTime = instanceLabel.substring(lastDashIndex + 3);
1706								content += '<div class="details"><i class="fa-regular fa-calendar-days"></i>&nbsp;' + dateTime + '</div>';
1707							}
1708						}
1709
1710						content += '</div><div class="table-cell price">- ' + formatForCurrency(currencySymbol, exchangeCreditDisplay) + '</div></div>';
1711					} else {
1712						content += '<div class="product"><div class="product-info">';
1713						if (seatLabelArgs) {
1714							content += '<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(' + seatLabelArgs + ');"><i class="fa fa-times" aria-hidden="true"></i></a>';
1715						} else {
1716							content += '<a class="remove-check" title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), \'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a>';
1717						}
1718						if (activityInfo == '') {
1719							content += '<strong><span class="quantity">' + quantity + 'x</span> ' + typeName + '</strong><br></div>';
1720						} else {
1721							content += '<strong>' + quantity + 'x ' + typeName + '</strong><br></div><div class="details">' + timeConflict + activityInfo + '</div>';
1722						}
1723						content += '<div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>';
1724					}
1725					break;
1726				case 'Classic':
1727				default:
1728					if (data['purchase_location'] == 'back-end' || data['purchase_location'] == 'voucher') {
1729						params = {
1730							removeIndex,
1731							formId,
1732							aittId,
1733							quantity,
1734							typeName,
1735							activityInfo,
1736							timeConflict,
1737							currencySymbol,
1738							selectedQuantityPrice,
1739							seatLabelArgs
1740						}
1741						content += buildBackendOrEExOrderSummaryItemRow(params);
1742					} else {
1743						content += '<tr class="ticketType">';
1744						if (seatLabelArgs) {
1745							content += '<td class="del"><a title="Remove" href="javascript:void(0);" onclick="chart.deselectSeats(' + seatLabelArgs + ');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>';
1746						} else {
1747							content += '<td class="del"><a title="Remove" href="javascript:void(0);" onclick="removeFromCart($(this), \'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>';
1748						}
1749						if(activityInfo == ''){
1750							content += '<td class="type">' + quantity + ' x ' + typeName;
1751							content += '</td><br>';
1752							content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>';
1753						} else {
1754							content += '<td class="type">' + quantity + ' x ' + typeName + '<div class="details">' + timeConflict + activityInfo + '</div>';
1755							content += '</td><br>';
1756							content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice)+'</td>';
1757						}
1758
1759						content += '</tr>';
1760					}
1761					break;
1762			}
1763		}
1764	});
1765
1766	$('.giftCardOptions').each(function(removeIndex) {
1767		var creditName = $(this).find('[name$="name]"]').val();
1768		var quantity = $(this).find('[name$="quantity]"]').val();
1769		var priceEach = $(this).find('[name$="price_each]"]').val();
1770
1771		if (priceEach) {
1772			priceEach = priceEach.replace(',','');
1773			selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2);
1774		} else {
1775			selectedQuantityPrice = '0.00';
1776		}
1777
1778		if(quantity && parseInt(quantity) > 0) {
1779			switch(tsTheme) {
1780				case 'Modern':
1781					content += '<div class="product"><div class="product-info">';
1782					content += '<a class="remove-check" title="Remove" href="javascript:removeFromCartGiftCard(\'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a>';
1783
1784					content += '<strong>' + quantity + 'x ' + creditName + '</strong><br></div>';
1785					content += '<div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>';
1786					break;
1787				case 'Classic':
1788				default:
1789					content += '<tr class="ticketType">';
1790					content += '<td class="del"><a title="Remove" href="javascript:removeFromCartGiftCard(\'' + removeIndex + '\', \'' + formId + '\', \'' + aittId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>';
1791					content += '<td class="type">' + quantity + ' x ' + creditName;
1792					content += '</td><br>';
1793					content += '<td class="quantity">'+formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>';
1794					content += '</tr>';
1795
1796					break;
1797			}
1798		}
1799
1800	});
1801
1802	// list the ticket types and their quantities (Ticket Type Dropdown View)
1803	$('.ticketOptions-singleDropdown [name$="quantity]"]').each(function() {
1804
1805		fieldName = $(this).attr('name');
1806		row = fieldName.match(/\d+/)[0];
1807		typeName = $('#Ticket' + row + 'TicketTypeName').val();
1808		priceEach = $('#Ticket' + row + 'PriceEach').val();
1809		quantity = $(this).val();
1810		selectedQuantityPrice = parseFloat(Math.round((priceEach*quantity) * 100) / 100).toFixed(2);
1811
1812		if(quantity && parseInt(quantity) > 0) {
1813			switch(tsTheme) {
1814				case 'Modern':
1815					content += '<div class="product"><div class="product-info"><a class="remove-check" href="javascript:void(0);" onclick="removeThisTicketType(' + row + ');"><i class="fa fa-times"></i></a><strong>' + quantity + 'x ' + typeName + '</strong><br></div><div class="product-totals">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</div><div class="clear"></div></div>';
1816					break;
1817				case 'Classic':
1818				default:
1819					content += '<tr class="ticketType">';
1820					content += '<td class="del"><a href="javascript:void(0);" onclick="removeThisTicketType(' + row + ');"><i class="fa fa-times"></i></a></td>';
1821					content += '<td class="type">' + quantity + ' x ' + typeName + '</td><br>';
1822					content += '<td class="quantity">' + formatForCurrency(currencySymbol, selectedQuantityPrice) + '</td>';
1823					content += '</tr>';
1824					break;
1825			}
1826		}
1827	});
1828
1829	// list the selected seats (if any)
1830	var seats = $('#OrderSelectedSeatLabels').val();
1831	if (seats !== undefined && seats.length > 0) {
1832		var seatingInfo = '';
1833		$.each(data.objectTypes, function(idx, obj){
1834			if (seatingInfo == '') {
1835				seatingInfo = obj;
1836			} else {
1837				seatingInfo += '<br>' + obj;
1838			}
1839		});
1840
1841		if (seatingInfo !== undefined && seatingInfo.length > 0) {
1842			switch(tsTheme) {
1843				case 'Modern':
1844					content += '<div class="product"><div class="product-info-seat"><strong>' + seatingInfo + '</strong><br></div><div class="clear"></div></div>';
1845					break;
1846				case 'Classic':
1847				default:
1848					content += '<tr>';
1849					content += '<td colspan="2">' + seatingInfo + '</td>';
1850					content += '</tr>';
1851					break;
1852			}
1853		}
1854	}
1855
1856	var charityName;
1857	var amount;
1858	$('.charity').each(function() {
1859		charityName = $(this).find('[name$="charity_name]"]').val();
1860		charityId = $(this).find('[name$="charity_event_id]"]').val();
1861		amount = $(this).find('[name$="total]"]').val();
1862		if (data.updatedData.Contribution && data.updatedData.Contribution.length && amount && !isNaN(parseFloat(amount)) && amount > 0) {
1863			switch(tsTheme) {
1864				case 'Modern':
1865					content += '<div class="product"><div class="product-info"><strong>' + donation + ': ' + charityName + '</strong><br></div><div class="product-totals">' + formatForCurrency(currencySymbol, amount) + '</div><div class="clear"></div></div>';
1866					break;
1867				case 'Classic':
1868				default:
1869					content += '<tr class="ticketType">';
1870					if (typeof seatedType == 'undefined' || !seatedType) {
1871						content += '<td class="del"><a title="Remove" href="javascript:removeCharityFromCart(\'' + charityId + '\', \'' + formId + '\');"><i class="fa fa-times" aria-hidden="true"></i></a></td><br>';
1872					}
1873					content += '<td class="type">' + donation + ': <span class="summary_charity_name">'+charityName+'</span></td><br>';
1874					content += '<td class="quantity">' + formatForCurrency(currencySymbol, amount) + '</td>';
1875					content += '</tr>';
1876			}
1877		} else {
1878			$(this).find('[name$="total]"]').val('');
1879			if (data.minDonationMsg) {
1880				alertify.error(data.minDonationMsg);
1881			}
1882		}
1883	});
1884
1885	if (data.time_conflicts.length > 0) {
1886		let conflictMessage = data.conflict_type === 'different_days'
1887			? 'Selected items are on different days'
1888			: 'There are scheduling conflicts in your cart';
1889		if (tsTheme === 'Modern') {
1890			content += '<div id="time-conflict-warning">* ' + conflictMessage + '</div>';
1891		} else {
1892			content += '<tr id="time-conflict-warning"><td colspan=3>* ' + conflictMessage + '</td></tr>';
1893		}
1894	}
1895
1896	if (content == '') {
1897		let isBackEnd = data['purchase_location'] == 'back-end';
1898		let isExchange = data['purchase_location'] == 'exchange';
1899		let isVoucher = data['purchase_location'] == 'voucher';
1900		let isModernTheme = tsTheme == 'Modern';
1901
1902		if (isBackEnd || isExchange || isVoucher) {
1903			content += '<div class="table-row"><div class="table-cell type"><div class="order_summary_default_message">' + noitems + '</div></div><div class="table-cell price"></div></div>';
1904		} else if (isModernTheme) {
1905			content += '<div class="order_summary_default_message"><p>' + noitems + '</p></div>';
1906		} else {
1907			content += '<tr><td class="t-align-right">' + noitems + '</td></tr>';
1908		}
1909	}
1910
1911	//sets default promo code value
1912	if (!data.promoCodeAmount || data.promoCodeAmount == "") {
1913		data.promoCodeAmount = "0";
1914	}
1915
1916
1917	// fades out, updates content, then fades back in (BOTH VERSIONS)
1918	$('.orderPriceDetails').fadeTo('fast', 0.4, function() {
1919		switch(tsTheme) {
1920			case 'Modern':
1921				$(".orderSummary").html(content);
1922				break;
1923			case 'Classic':
1924			default:
1925				if (data['purchase_location'] == 'back-end' || data['purchase_location'] == 'voucher') {
1926					$(".orderSummary").html(content);
1927				} else {
1928					$(".orderSummary table").html(content);
1929				}
1930				break;
1931		}
1932
1933		$('.customFeesItem').remove();
1934		if (typeof data.CustomFeesOrders != 'undefined') {
1935			bundledFees = 0;
1936			$.each(data.CustomFeesOrders, function(i, item) {
1937				if (data.bundle_fees < 2) {
1938					switch(tsTheme) {
1939						case 'Modern':
1940							$('.orderTax').before('<div class="customFeesItem priceDetail"><div class="title">' + data.CustomFeesOrders[i].custom_fee_name + '</div><div class="value">' + currencySymbol + data.CustomFeesOrders[i].actual_fee_applied + '</div></div>');
1941							break;
1942						case 'Classic':
1943						default:
1944							$('.orderTax').before('<tr class="customFeesItem"><td class="title">' + data.CustomFeesOrders[i].custom_fee_name + '</td><td class="value">' + currencySymbol + data.CustomFeesOrders[i].actual_fee_applied + '</td></tr>');
1945							break;
1946					}
1947				} else {
1948					bundledFees = Number(bundledFees) + Number(data.CustomFeesOrders[i].actual_fee_applied);
1949				}
1950			});
1951		}
1952
1953		$(".orderPriceDetails .promoCode .value").html("()" + currencySymbol + data.promoCodeAmount + ")");
1954
1955		// order subtotal
1956		$(".orderPriceDetails .orderSubtotal .value").html(formatForCurrency(currencySymbol, data.subTotal));
1957		if(data.subTotal > 0) {
1958			$(".orderPriceDetails .orderSubtotal").show();
1959		} else {
1960			$(".orderPriceDetails .orderSubtotal").hide();
1961		}
1962
1963		$(".orderPriceDetails .orderExchange .value").html('(' + formatForCurrency(currencySymbol, exchangeCreditDisplay) + ')');
1964		if(data.exchange_credit > 0) {
1965			$(".orderPriceDetails .orderExchange").show();
1966		} else {
1967			$(".orderPriceDetails .orderExchange").hide();
1968		}
1969
1970		// ticket insurance
1971		$(".orderPriceDetails .totalInsuranceFee .value").html(formatForCurrency(currencySymbol, data.totalInsuranceFee));
1972		if(data.totalInsuranceFee > 0) {
1973			$(".orderPriceDetails .totalInsuranceFee").show();
1974		} else {
1975			$(".orderPriceDetails .totalInsuranceFee").hide();
1976		}
1977
1978		// gratuities
1979		$(".orderPriceDetails .totalGratuity .value").html(formatForCurrency(currencySymbol, data.totalGratuityAmount));
1980		if(data.totalGratuityAmount > 0) {
1981			$(".orderPriceDetails .totalGratuity").show();
1982		} else {
1983			$(".orderPriceDetails .totalGratuity").hide();
1984		}
1985
1986		// order price details
1987		$(".orderPriceDetails .orderTax .value").html(formatForCurrency(currencySymbol, data.tax));
1988		if(data.tax > 0) {
1989			$(".orderPriceDetails .orderTax").show();
1990		} else {
1991			$(".orderPriceDetails .orderTax").hide();
1992		}
1993
1994		if (data.bundle_fees == 1) {
1995			$(".orderPriceDetails .orderServiceFees").hide();
1996			$(".orderPriceDetails .orderCCFees").hide();
1997			$(".orderPriceDetails .orderServiceCharges .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.serviceCharge * 100) / 100).toFixed(2)));
1998			if (data.serviceCharge > 0) {
1999				$(".orderPriceDetails .orderServiceCharges").show();
2000			} else {
2001				$(".orderPriceDetails .orderServiceCharges").hide();
2002			}
2003		} else if (data.bundle_fees == 2) {
2004			bundledFees = Number(bundledFees) + Number(data.serviceCharge);
2005			$(".orderPriceDetails .orderServiceFees").hide();
2006			$(".orderPriceDetails .orderCCFees").hide();
2007			$(".orderPriceDetails .orderServiceCharges .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(bundledFees * 100) / 100).toFixed(2)));
2008			if (bundledFees > 0) {
2009				$(".orderPriceDetails .orderServiceCharges").show();
2010			} else {
2011				$(".orderPriceDetails .orderServiceCharges").hide();
2012			}
2013		} else {
2014			$(".orderPriceDetails .orderServiceFees .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.admin_charges * 100) / 100).toFixed(2)));
2015			if(data.admin_charges > 0) {
2016				$(".orderPriceDetails .orderServiceFees").show();
2017			} else {
2018				$(".orderPriceDetails .orderServiceFees").hide();
2019			}
2020
2021			$(".orderPriceDetails .orderCCFees .value").html(formatForCurrency(currencySymbol, parseFloat(Math.round(data.ccFees * 100) / 100).toFixed(2)));
2022			if(data.ccFees > 0) {
2023				$(".orderPriceDetails .orderCCFees").show();
2024			} else {
2025				$(".orderPriceDetails .orderCCFees").hide();
2026			}
2027		}
2028
2029
2030		if(data.hasOwnProperty('promoCodeAmount')) {
2031			//To fix a problem where promo codes over $999 were showing up as NaN,
2032			//need to remove any commas because parseFloat doesn't deal with commas correctly
2033			var promoCodeAmount = parseFloat(data.promoCodeAmount.replace(',',''));
2034			//then we will calculate the correct amount to present
2035			promoCodeAmount = parseFloat(Math.round(promoCodeAmount * 100) / 100).toFixed(2);
2036			//then format with currency sign and commas as appropriate
2037			promoCodeAmount = currencySymbol + promoCodeAmount.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
2038			$(".orderPriceDetails .promoCode .value").html("(" + promoCodeAmount + ")");
2039		}
2040		if($('#real-promo-field').val()) {
2041			$(".orderPriceDetails .promoCode").show();
2042		} else {
2043			$(".orderPriceDetails .promoCode").hide();
2044		}
2045
2046		var newTotal = data.total;
2047		if(data.gift_card_amount > 0) {
2048			$(".giftCard").slideDown();
2049			$(".giftCard .value").html('(' + formatForCurrency(currencySymbol, data.gift_card_amount) + ')');
2050		} else {
2051			$(".giftCard").slideUp();
2052			$(".giftCard .value").html();
2053		}
2054
2055		$(".orderPriceDetails .orderTotal .value").html(formatForCurrency(currencySymbol, data.total));
2056		$("#orderTotalReview .amount").html(formatForCurrency(currencySymbol, data.total));
2057
2058		// update the refund protection "No" option with the order total that
2059		// excludes the BP premium, so it shows what the buyer would pay if they decline
2060		if (ticketInsuranceJsLoaded && typeof formatter !== 'undefined') {
2061			const declineTotal = (typeof data.total_without_insurance !== 'undefined') ? data.total_without_insurance : data.total;
2062			$('.ti-order-total').html(formatter.format(declineTotal));
2063		}
2064
2065		// ADDED FOR NAV ORDER TOTAL
2066		if(data.total == 0){
2067			// IF THE TOTAL IS EMPTY, DO NOT SHOW HEADER TOTAL
2068			$(".nav-order-total").slideUp();
2069			$(".nav-order-total .amount").html(formatForCurrency(currencySymbol, data.total));
2070		} else {
2071			$(".nav-order-total .amount").html(formatForCurrency(currencySymbol, data.total));
2072			$(".nav-order-total").slideDown();
2073		}
2074
2075		$("#OrderProposedTotal").val(data.total);
2076		$(".orderPriceDetails .ajax_loader").fadeOut('fast');
2077		$(".orderPriceDetails").fadeTo('fast', 1);
2078		$(".orderSummary").fadeTo('fast', 1);
2079
2080		$(".promo_msg").html(data.promoCodeMessage);
2081	}).promise().done(function() {
2082		// no need to run this mulitple times, just run once fadeTo is finished
2083		if (Number(data.total) == 0 && ticketInsuranceJsLoaded) {
2084			// order is free, clear and hide any reference to insurance
2085			clearInsuranceQuote();
2086			hideShowInsuranceWidget('hide');
2087		}
2088	});
2089
2090	//show or hide promo code line-item
2091	if(data.promoCodeAmount > 0) {
2092		$(".orderPriceDetails .promoCode").show();
2093	} else {
2094		$(".orderPriceDetails .promoCode").hide();
2095	}
2096
2097	var hideGiftCard = true;
2098
2099	switch(data.giftCardStatus) {
2100		case 'success':
2101			$('#giftCardNumberResponse').html('Gift Card Applied').css('color', '#038514').css('display', 'block');
2102			var hideGiftCard = false;
2103			break;
2104		case 'failed':
2105			$('#giftCardNumberResponse').html('Gift Card not allowed for this purchase.').css('color', '#D12E5A').css('display', 'block');
2106			$('#OrderCreditId').val('');
2107            var hideGiftCard = false;
2108			break;
2109		case 'invalid':
2110			$('#giftCardNumberResponse').html('Invalid Gift Card Number').css('color', '#D12E5A').css('display', 'block');
2111			$('#OrderCreditId').val('');
2112            var hideGiftCard = false;
2113			break;
2114		default :
2115			$('#giftCardNumberResponse').html('').css('color', '#038514').css('display', 'block');
2116			$('#OrderCreditId').val('');
2117            var hideGiftCard = false;
2118			break;
2119	}
2120
2121	$("#OrderProposedTotal").val(data.total);
2122
2123	if(data.total == '0.00' || data.cash_option) {
2124		if(data.total > 0) {
2125			showPaymentFields();
2126		} else {
2127			hidePaymentFields(hideGiftCard);
2128		}
2129		hideCCFields();
2130	} else {
2131		if(data.total > '0.00'){
2132			if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') {
2133				// Caller owns the PaymentIntent on this tick; skip to avoid racing it.
2134				if (typeof suppressPaymentIntentRefresh !== 'undefined' && suppressPaymentIntentRefresh) {
2135					clearSuppressPaymentIntentRefresh();
2136				} else if ($('#OrderPaymentIntentId').val()) {
2137					// if already have payment intent update the payment intent, unless a submitted
2138					// order is mid-payment - its amount was set when the order was submitted
2139					if (!orderSubmissionInProgress) {
2140						updatePaymentIntent();
2141					}
2142				} else if (typeof modalSteps === 'undefined' && data.purchase_location != 'voucher') {
2143					// if not modal, load payment element, modal will load when switching to that view
2144					initialize();
2145				}
2146			}
2147			var giftCardSetting = false;
2148			if(data.totalTicketProducts > 0){
2149				giftCardSetting = true;
2150			}
2151			showCCFields(giftCardSetting);
2152		} else {
2153			hideCCFields();
2154		}
2155	}
2156
2157	if (data.cash_option && data.include_fees == 4) {
2158		$('.orderServiceFees').slideUp();
2159		$('.orderServiceCharges').slideUp();
2160	}
2161
2162}
2163
2164function formatForCurrency(symbol, data){
2165	switch(symbol) {
2166		case 'CAD$':
2167			return 'CAD$' + data;
2168		case '£':
2169			return '£' + data;
2170		case 'NZ$':
2171			return 'NZ$' + data;
2172		case '€':
2173			// converts the number to display in the summaries in the correct format for 'EUR'
2174			var currencyConverted = data.toString().split(".");
2175			currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, ".");
2176			data = currencyConverted.join(",");
2177			return '€' + data;
2178		case 'R':
2179			// converts the number to display in the summaries in the correct format for 'EUR'
2180			var currencyConverted = data.toString().split(".");
2181			currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
2182			data = currencyConverted.join(",");
2183			return 'R' + data;
2184		default:
2185			var currencyConverted = data.toString().split(",");
2186			currencyConverted[0] = currencyConverted[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2187			data = currencyConverted.join(",");
2188			return '$' + data;
2189	}
2190}
2191
2192
2193// only needed where has both cash and credit options and front end exchange
2194function hidePaymentFields(hideGiftCard) {
2195	if($('#creditCardFields').parent().attr('id') == 'payment-fields') {
2196		$('#promo-code-field').slideUp();
2197		$('#giftCardPaymentField').slideUp();
2198	}
2199	if(hideGiftCard){
2200		$('#giftCardPaymentField').slideUp();
2201	}
2202
2203	if($('.payment-plan-message').length){
2204		$('.payment-plan-message').slideUp('fast');
2205	}
2206
2207	if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') {
2208		enableSubmit(origSubmitButtonText);
2209	}
2210}
2211
2212
2213/**
2214 *
2215 * @param {*} giftCardSetting
2216 */
2217function showPaymentFields(giftCardSetting) {
2218	if($('#creditCardFields').parent().attr('id') == 'payment-fields') {
2219		$('#payment-fields').slideDown();
2220		$('#promo-code-field').slideDown();
2221		$('#giftCardPaymentField').slideDown();
2222	}
2223	if(giftCardSetting){
2224		$('#giftCardPaymentField').slideDown();
2225	} else {
2226		$('#giftCardPaymentField').slideUp();
2227	}
2228
2229	// check that it is stripe
2230	if(typeof paymentGateway != 'undefined' && paymentGateway == 'stripe') {
2231		// Wait-list requests collect no card payment and render no Stripe payment element,
2232		// so there's nothing to validate — keep the submit button enabled rather than gating
2233		// it on a payment element that will never report "complete".
2234		const paymentElement = document.getElementById('payment-element');
2235		// check the last known status of the payment element to determine what is supposed to happen with the submit button
2236		if(!paymentElement || (typeof paymentElementLastStatus != 'undefined' && paymentElementLastStatus.complete)){
2237			enableSubmit(origSubmitButtonText);
2238		} else {
2239			disableSubmit(origSubmitButtonText);
2240		}
2241	}
2242}
2243
2244function showCCFields(giftCardSetting) {
2245	var paymentType = $('#OrderPaymentType').val();
2246	$('#creditCardFields').slideDown();
2247	$('#auth-net-seal-area').slideDown();
2248	$('#payment-options').slideDown();
2249
2250	if (paymentType == 'creditCard' || paymentType == 'iDEAL'){
2251		switch(paymentGateway){
2252			case 'stripe':
2253				break;
2254			case 'authnet':
2255				$('#OrderCcNumber').removeAttr('disabled');
2256				$('#OrderCcExpireMonth').removeAttr('disabled');
2257				$('#OrderCcExpireYear').removeAttr('disabled');
2258				$('#OrderCcCvv').removeAttr('disabled');
2259				break;
2260			case 'square':
2261				buildForm();
2262				break;
2263		}
2264	}
2265	showPaymentFields(giftCardSetting);
2266}
2267
2268function hideCCFields() {
2269	if(!$('#creditCardFields').attr('donthide')) {
2270		$('#creditCardFields').slideUp();
2271		$('#auth-net-seal-area').slideUp();
2272		$('#payment-options').slideUp();
2273		if(typeof paymentGateway != 'undefined' && paymentGateway == 'authnet') {
2274			$('#OrderCcNumber').attr('disabled', 'disabled');
2275			$('#OrderCcExpireMonth').attr('disabled', 'disabled');
2276			$('#OrderCcExpireYear').attr('disabled', 'disabled');
2277			$('#OrderCcCvv').attr('disabled', 'disabled');
2278		}
2279	}
2280	hidePaymentFields(false);
2281}
2282
2283function togglePaymentFieldsForExchange() {
2284	//display the payment fields
2285	$('#paymentInformation').toggle();
2286	if($('#paymentInformation').is(":visible")){
2287		setTimeout(function() {
2288			$(window).scrollTop($(document).height());
2289		}, 500); // Scroll to payment info for better UX
2290		$('#checkoutShowButton').html(modifyHidePaymentFields);
2291	} else {
2292		$('#checkoutShowButton').html(modifyShowPaymentFields);
2293	}
2294}
2295
2296
2297function focusImage(){
2298	$('body').scrollTop($("#photo-upload").offset().top - 20);
2299}
2300
2301// any logic that needs to happen before the order is submitted
2302function submitOrder() {
2303
2304	warnAgainstLeaveAttempts();
2305
2306	//check whether our global translated strings are in memory - if one is missing they'll all be missing
2307	if (typeof SOerror == 'undefined') {
2308		SOerror = 'Please make sure everything is filled out and try again.';
2309		SOerror1 = 'You must select at least one ticket/registration.';
2310		SOerror2 = 'Please make sure all required questions are answered.';
2311		SOerror3 = 'This order exceeds the maximum number of complimentary tickets on this event by ';
2312		SOerror4 = 'Please contact your support representative to increase your allotment. Fees may apply.';
2313		SOvalidationErrorMsg = 'Please fix this field';
2314		SOphotoUpload = 'Required Photo Upload';
2315		SOimageTextSingular = 'image';
2316		SOimageTextPlural = 'images';
2317		SOphotoUpload1 = 'You have not uploaded the ';
2318		SOphotoUpload2 = ' required to submit this order.';
2319		SOsubmittingText = 'Submitting Your Order...';
2320		SOpaymentAuthText = 'Sending for payment authorization!';
2321		SOsuccessText = 'Success!';
2322	}
2323	var error = SOerror;
2324	var useThisAlertBox = true;
2325	var shouldSubmit = true;
2326	var validationErrorMsg = SOvalidationErrorMsg; //this doesn't appear to be used anywhere at all, actually
2327
2328	// checking for empty gratuity field
2329	var gratuityIssues = [];
2330	$('.gratuity-value').each(function(){
2331		if(!$(this).is(':disabled')){
2332			if($(this).val() == ''){
2333				gratuityIssues['notSelected'] = true;
2334				gratuityIssues['optionTitle'] = $(this).data('option-title');
2335			}
2336		}
2337	});
2338
2339	if(gratuityIssues['notSelected']){
2340		var optionTitle = gratuityIssues['optionTitle'];
2341		alertify.error('You must select a ' + optionTitle + ' option');
2342		var aTag = $("a[name='gratuity']");
2343		$('html,body').animate({scrollTop: aTag.offset().top-100},'slow');
2344		ignoreLeaveAttempts();
2345		enableSubmit(origSubmitButtonText);
2346		return false;
2347	}
2348
2349	// CHECKING ORDER LEVEL SUBMISSION FOR REQUIRED IMAGE UPLOAD
2350	if($('#OrderPhotosUploadRequired').length){
2351		// IF THE FIELD IS TRUE, NOTHING TO DO
2352		if($('#OrderPhotosUploadRequired').val() == 'true'){
2353			// PHOTO REQUIREMENTS HAVE BEEN
2354		} else {
2355			// IF THE FIELD IS ANYTHING ELSE - DO NOT CONTINUE WITH THE SUBMIT.
2356			var imageText = SOimageTextPlural;
2357			if($('#OrderMinPhotosUpload').val() < 2){
2358				imageText = SOimageTextSingular;
2359			}
2360			alertify
2361				.alert()
2362				.setting({
2363				'title' : SOphotoUpload,
2364				'message': SOphotoUpload1 + $('#OrderMinPhotosUpload').val() + ' ' + imageText + SOphotoUpload2,
2365				'modal': true,
2366				'basic': false,
2367				'closable': true,
2368				'closableByDimmer': false,
2369			})
2370				.set({
2371				onclose: function() {
2372					enableSubmit(origSubmitButtonText);
2373					setTimeout(focusImage, 500);
2374
2375					return false;
2376				},
2377			})
2378				.show();
2379			ignoreLeaveAttempts();
2380			return false;
2381		}
2382	}
2383
2384	// ensure credit card passes any cc restrictions (if there even is a credit card field)
2385	var orderType = $('#OrderPaymentType').val();
2386	var orderStatus = 0;
2387
2388	if($('#OrderStatus').val()){
2389		orderStatus = $('#OrderStatus').val();
2390	}
2391
2392	// force not iDEAL if total is zero
2393	if($('#OrderProposedTotal').val()==0 && (orderType == 'iDEAL')) $('#OrderPaymentType').val('creditCard');
2394
2395	// prevent order submission if no quantity has been selected (except for waitlist orders which will not have a selected quantity here)
2396	orderTotQty = orderTotalQuantity();
2397	orderIsWaitlist = $('#OrderPurchaseLocation').val() === 'waitlist-confirmation';
2398	if (!orderIsWaitlist && orderTotQty <= 0 ) {
2399		shouldSubmit = false;
2400		error = SOerror1;
2401
2402	} else if( !requiredChecksChecked() ) {
2403		shouldSubmit = false;
2404		error = SOerror2;
2405	} else if ($('#ExtraRemainingComp').val() < orderTotQty) {
2406		shouldSubmit = false;
2407		error = SOerror3 + (orderTotQty - $('#ExtraRemainingComp').val()) + '. ' + SOerror4;
2408		//needs its own error box - the message won't fit in the normal one
2409		alertify.alert("Maximum Reached", error);
2410		ignoreLeaveAttempts();
2411		return false;
2412	}
2413
2414	// check if the opt in status is yes, if not
2415	if(ticketInsuranceJsLoaded && $('#OrderInsuranceOptStatus').val() == 0){
2416		// clear the policy total
2417		$('#OrderPolicyTotal').val(0);
2418	}
2419
2420	if ( !shouldSubmit ) {
2421		if(useThisAlertBox) {
2422			alertify.alert("Notice", error).set('modal', true).set('basic', false).pin();
2423		}
2424		ignoreLeaveAttempts();
2425		enableSubmit(origSubmitButtonText);
2426		return false;
2427	}
2428
2429	submittingAlert = alertify.alert()
2430	.setting({
2431		'message': SOsubmittingText,
2432		'modal': true,
2433		'closable': false,
2434		'closableByDimmer': false,
2435		'movable': false,
2436		'basic': true,
2437	})
2438	.set('onok', function(closeEvent) { return false; } )
2439	.set('onclose', function() { return true }) // to keep the 'fail' one from retaining old data on close
2440	.show();
2441	// .minimalDialog();
2442	// 'frameless': true,
2443
2444	// var error = 'Please make sure all required questions are answered.';
2445	// alertify.alert("Notice", error).set('modal', true).pin();
2446
2447	var layoutSlug = $('#OrderLayoutSlug').val();
2448	if(layoutSlug == 'layout_modern'){
2449		//deactivate submit button
2450		submitButtonAction('disable');
2451	}
2452
2453	var fieldsToSubmit = getOrderFormDataWithoutUnmodifiedZeroQuantityTickets('#orderForm');
2454
2455	// Apple Pay needs the wallet sheet to open inside the original click gesture,
2456	// so fire elements.submit() before the order-create ajax — but only when this
2457	// order will actually charge a card (skip cash, free, voucher).
2458	const purchaseLocation = $('#OrderPurchaseLocation').val();
2459	const orderChargesCard = parseFloat($('#OrderProposedTotal').val()) > 0
2460		&& !($('#OrderCashOption').val() && $('#OrderCashOption').prop('checked'))
2461		&& purchaseLocation !== 'voucher';
2462
2463	const needsEarlyElementsSubmit = typeof elements !== 'undefined' && elements
2464		&& typeof paymentGateway !== 'undefined' && paymentGateway === 'stripe'
2465		&& document.getElementById('payment-element')
2466		&& orderChargesCard;
2467
2468	const preSubmit = needsEarlyElementsSubmit ? elements.submit() : Promise.resolve({});
2469
2470	preSubmit.then(function(submitResult) {
2471		if (submitResult && submitResult.error) {
2472			// Bad payment details (e.g. resubmit after a decline) - show why, don't bail silently.
2473			submittingAlert.close();
2474			ignoreLeaveAttempts();
2475			enableSubmit(origSubmitButtonText);
2476			showPreSubmitError(submitResult.error.message);
2477			return;
2478		}
2479		orderSubmissionInProgress = true;
2480
2481		// submits the order via ajax
2482		$.ajax({
2483		type: "POST",
2484		url: buildURL('/orders/ajax_submit_order/' + $('#OrderEventId').val() + '/' + $('#OrderEventSlug').val()),
2485		data: fieldsToSubmit,
2486		dataType: "json",
2487		success: function(data) {
2488            switch(data['status']) {
2489				case 'success':
2490
2491					if(isModal()) {
2492					handleModalOrderComplete();
2493					}
2494
2495					if (data.gateway == 'authnet') {
2496						// successful normal order, setup redirect etc.
2497						handlePaymentRedirect(data);
2498					} else if(data.gateway == 'stripe') {
2499						orderData = data;
2500						var $form = $("#orderForm");
2501						$form.append($('<input type="hidden" name="data[Order][id]" id="OrderId">').val(data.order_id));
2502						if(data.redirect && data.cash_option == 1) {
2503							ignoreLeaveAttempts();
2504							window.location = data.redirect;
2505						} else {
2506							// if it has a balance paid and is not waitlist
2507							if(data.total_paid > 0 && data.order_type != 'waitlist'){
2508								ignoreLeaveAttempts();
2509								var processStatus = handleSubmit();
2510							} else {
2511								// if total paid is 0 or it is a waitlist, no need for payment
2512								ignoreLeaveAttempts();
2513								handlePaymentRedirect(data);
2514							}
2515						}
2516					}
2517
2518					break;
2519
2520				case 'error':
2521				case 'fail':
2522				case false:
2523				default:
2524					orderSubmissionInProgress = false;
2525					if(data['payment_failure'] != 'undefined' && data['payment_failure']){
2526						clearOutSeatedFields();
2527					}
2528					ignoreLeaveAttempts();
2529					if(data['redirect']) {
2530						window.location = data['redirect'];
2531					}
2532					submittingAlert.close();
2533					if(data['reason']) {
2534						data.refresh = true;
2535						alertAndClose(data);
2536					}
2537
2538
2539					if(data['fieldEnable'] == true && paymentGateway == 'authnet'){
2540						$( "#OrderCcNumber" ).prop( "disabled", false );
2541						$( "#OrderCcCvv" ).prop( "disabled", false );
2542					}
2543
2544					if (data['orderTicketLimitPassed'] == true) {
2545						var qtyTicketLimitTitle = 'Order Limit Exceeded';
2546						// var qtyTicketLimitMsg = 'Orders on this event are limited to ' + data['orderTicketLimit'] + ' items. Please adjust your quantities.';
2547						var qtyTicketLimitMsg = 'You’ve exceeded the quantity limits on this event. Please adjust your quantities and try again.'; //CZ - temp generic message for Kokefest launch April 2022, TODO: allow client to customize failure message
2548
2549						alertify.alert(qtyTicketLimitTitle, qtyTicketLimitMsg).setting({
2550							'message': qtyTicketLimitMsg,
2551							'modal': true,
2552							'basic': false,
2553							'closable': true,
2554							'closableByDimmer': false})
2555							.set({
2556								onok: function(closeEvent) { return true; }, // i think it didn't jump down before having to add this
2557							});
2558					}
2559					enableSubmit(origSubmitButtonText);
2560					break;
2561			}
2562			return false;
2563		},
2564
2565		});
2566	}).catch(function(submitError) {
2567		// elements.submit() rejected (network/internal) - bail, but tell the buyer why.
2568		submittingAlert.close();
2569		ignoreLeaveAttempts();
2570		enableSubmit(origSubmitButtonText);
2571		showPreSubmitError(submitError && submitError.message ? submitError.message : null);
2572	});
2573
2574	return false;
2575
2576	// return true;
2577}
2578
2579/**
2580 * Function to handle the payment redirect after a successful order.
2581 * It checks if the orderData object has a redirect property, and if it does,
2582 * it sets the confirmationUrl to the redirect URL. If it doesn't, it constructs
2583 * the confirmation URL using the order_id and lang (if available).
2584 *
2585 * @param {Object} orderData - The object containing the order data.
2586 * @param {string} orderData.order_id - The order ID.
2587 * @param {string} orderData.id - The order ID (in case order_id is not available).
2588 * @param {string} orderData.redirect - The redirect URL (optional).
2589 * @param {string} orderData.lang - The language (optional).
2590 *
2591 * @returns {void}
2592 */
2593 function handlePaymentRedirect(orderData) {
2594	 let thisOrderId;
2595	 if (orderData.order_id) {
2596		 thisOrderId = orderData.order_id;
2597	 } else {
2598		 thisOrderId = orderData.id;
2599	 }
2600	// successful normal order, setup redirect etc.
2601	if (orderData === undefined || !orderData.redirect) {
2602		let urlString = "/orders/confirmation/" + thisOrderId;
2603		if (orderData.lang) {
2604			urlString += "/" + orderData.lang;
2605		}
2606		var confirmationUrl = buildURL(urlString);
2607	} else {
2608		var confirmationUrl = buildURL(orderData.redirect);
2609	}
2610
2611	confirmationUrl = confirmationUrl + "/ordered=true";
2612
2613	 ignoreLeaveAttempts();
2614
2615	 // needed only for exchange, to refresh the confirmation page when the transaction is complete.
2616	 if ($('#OrderPurchaseLocation').val() == 'exchange') {
2617		 parent.location.reload();
2618		 parent.jQuery.fancybox.close();
2619		 return;
2620	 }
2621
2622	if (typeof isFrame !== 'undefined' && isFrame) {
2623		var emailAddon = '?email=' + $('#OrderEmail').val();
2624
2625		window.location = confirmationUrl + emailAddon;
2626	} else {
2627		window.location = confirmationUrl;
2628
2629		alertify
2630			.alert()
2631			.setting({
2632				'message': SOsuccessText,
2633				'modal': true,
2634				'closable': false,
2635				'closableByDimmer': true,
2636				'movable': false,
2637				'basic': true
2638			})
2639			.set('onok', function (closeEvent) { window.location = confirmationUrl; return false; }) // in case they hit escape to stop the initial redirect, it will trigger "onok" which triggers from "esc" key
2640			.show();
2641	}
2642}
2643
2644// Shows an elements.submit() failure so a bailed submit isn't a silent no-op.
2645function showPreSubmitError(message) {
2646	if (!message) {
2647		message = 'There was a problem with your payment details. Please check your card and try again.';
2648	}
2649	$('#payment-response').text(message).addClass('error'); // in-line payment response banner
2650	alertAndClose({ reason: message, refresh: false });
2651}
2652
2653/**
2654 *
2655 * @param {*} data
2656 */
2657function alertAndClose(data) {
2658	let refresh = data.refresh;
2659	let redirect = data.redirect;
2660	let redirect_url = data.redirect_url;
2661	let clearPrefill = data.clearPrefill;
2662
2663	alertify
2664		.alert("Order Error", data['reason'])
2665		.setting({
2666			'message': data['reason'],
2667			'modal': true,
2668			'basic': false,
2669			'closable': true,
2670			'closableByDimmer': false,
2671		})
2672		.set({
2673			onshow: null,
2674			onok: function (closeEvent) { return true; },
2675			onclose: function () {
2676				addValidationErrors(data['validation_errors']);
2677				//if this is a timed entry / selective registration failure, we need to empty the cart and reload the page
2678				if (clearPrefill === true) {
2679					//use an ajax call to clear out the prefill
2680					$.ajax({
2681						type: "GET",
2682						url: buildURL("/admin/orders/ajax_clear_prefill/"),
2683						dataType: "json",
2684						success: function (data) {
2685							ignoreLeaveAttempts();
2686
2687							if(refresh === true){
2688								//need to get the window to reload WITHOUT previous POST data
2689								window.location.href = window.location.href;
2690							}
2691
2692							// used on the return page for ideal and payment plan payments
2693							if(redirect === true){
2694								//need to get the window to reload WITHOUT previous POST data
2695								window.location.replace(redirect_url);
2696							}
2697						},
2698						error: function (errorMessage) {
2699							return false;
2700						}
2701					});
2702				}
2703			},
2704		})
2705		.show();
2706}
2707
2708// adds a validation error/message to a specified field
2709// and adds an on keyup to remove it
2710function addValidationErrors(validationErrors) {
2711	var focusedFirstField = false;
2712	removeAllValidationErrors();
2713	if(validationErrors) {
2714		$.each(jQuery.parseJSON(validationErrors), function( fieldName, error) {
2715			removeValidationError(fieldName);
2716			var validationErrorMsg = '<div class="error-message">' + error[0] + '</div>';
2717			var field = $('[name="data[Order][' + fieldName + ']"]');
2718			if(field.length > 0) {
2719				field.after(validationErrorMsg);
2720				field.addClass('form-error');
2721				switch (field.prop('type')) {
2722					case 'radio':
2723					case 'select-one':
2724					case 'select':
2725					case 'select-multiple':
2726						field.on('change', function () {
2727							removeValidationError(fieldName);
2728						});
2729						break;
2730
2731					case 'text':
2732					default:
2733						field.on('keyup', function () {
2734							removeValidationError(fieldName);
2735						});
2736						break;
2737				}
2738				if (focusedFirstField == false) {
2739					field.trigger("focus");
2740					$('body').scrollTop(field.offset().top);
2741					focusedFirstField = true;
2742				}
2743			}
2744		});
2745	}
2746}
2747
2748// removes the validation error on a specific field
2749function removeValidationError(fieldName) {
2750	var field = $('[name="data[Order][' + fieldName + ']"]');
2751	field.siblings('.error-message').remove();
2752	field.removeClass('form-error');
2753}
2754
2755// removes all validations errors, resetting the form from errors
2756function removeAllValidationErrors() {
2757	var formErrorAreas = $('#orderForm .form-error');
2758	$('#orderForm .error-message').remove();
2759	$('#orderForm .form-error').removeClass('form-error');
2760}
2761
2762
2763
2764// makes sure that any multi-checkboxes that are required have at least one checked
2765function requiredChecksChecked() {
2766	var toReturn = true;
2767	$('ul.check-required').each(function(index, ul) {
2768		var atLeastOneChecked = false;
2769		$(this).find('input').each(function(index, input) {
2770			if($(input).prop('checked')) {
2771				atLeastOneChecked = true;
2772			}
2773		});
2774		if(!atLeastOneChecked) {
2775			$(ul).addClass('requiredAlert');
2776			toReturn = false;
2777		} else {
2778			$(ul).removeClass('requiredAlert');
2779		}
2780	});
2781	return toReturn;
2782}
2783
2784
2785/**
2786 * [orderTotalQuantity description]
2787 * @return {[type]} [description]
2788 */
2789function orderTotalQuantity() {
2790	var totalQuantity = 0;
2791	var quantity;
2792	$('[name^="data[Ticket]"][name$="[quantity]"]').each(function() {
2793		quantity = $(this).val();
2794		if(quantity && quantity.length > 0) {
2795			totalQuantity += parseInt($(this).val());
2796		}
2797	});
2798	$('.charity [name$="total]"]').each(function() {
2799		quantity = $(this).val();
2800		if(quantity && quantity.length > 0) {
2801			totalQuantity += parseInt($(this).val());
2802		}
2803	});
2804	$('.giftCardOptions [name$="quantity]"]').each(function() {
2805		quantity = $(this).val();
2806		if(quantity && quantity.length > 0) {
2807			totalQuantity += parseInt($(this).val());
2808		}
2809	});
2810
2811	return totalQuantity;
2812
2813}
2814
2815
2816// shows or hides multiple ticket type prices
2817function toggleMultiPrices(id) {
2818	if ($('.ticket-type-prices-'+id).is(':visible')) {
2819		$('.ticket-type-prices-'+id).slideUp();
2820		$('.btn-buy-tickets-'+id).removeClass('btn-default');
2821		$('.btn-buy-tickets-'+id).addClass('btn-primary');
2822	} else {
2823		$('.ticket-type-prices-'+id).slideDown();
2824		$('.btn-buy-tickets-'+id).removeClass('btn-primary');
2825		$('.btn-buy-tickets-'+id).addClass('btn-default');
2826	}
2827}
2828
2829
2830// called when the calculate_total recognizes that there are no items selected
2831function quantityChangedToZero() {
2832	if(ticketInsuranceJsLoaded){
2833		hideShowInsuranceWidget('hide');
2834	}
2835}
2836
2837// called when the calculate_total recognizes that there is at least one ticket selected
2838function quantityChagnedToAtLeastOne() {
2839	if(ticketInsuranceJsLoaded){
2840		hideShowInsuranceWidget('show');
2841	}
2842}
2843
2844
2845function update_selected_charity(charityId) {
2846	var name = $('#Contribution0CharityId option[value="'+charityId+'"]').text();
2847
2848	$('#Contribution0CharityName').val(name);
2849	$('.summary_charity_name').html(name);
2850
2851	// hide all charity descriptions
2852	$('.charity_description').slideUp('medium', function() {
2853		// show the correct charity description
2854		$('#charity_description_'+charityId).slideDown('medium');
2855	});
2856
2857}
2858
2859
2860// gets the 'quantity' field for the selected seat object / selectedTicketType object (passed from seating chart)
2861// called from on both selecting and deselecting
2862function getQuantityFieldOfSelectedSeat(seat, selectedTicketType) {
2863
2864	var quantityFieldId = false;
2865
2866	// if a specific ticket type price was selected, increment it's quantity
2867	if (selectedTicketType !== null && typeof selectedTicketType === 'object') {
2868		var ticketTypePriceKeys = ticketTypeCategories[seat['category']['key']]['ticket_type_price_keys'];
2869		for (index = 0; index < ticketTypePriceKeys.length; ++index) {
2870			if (selectedTicketType['ticketType'] == ticketTypePrices[ticketTypePriceKeys[index]]['name']) {
2871				quantityFieldId = get_quantity_field_id(null, ticketTypePrices[ticketTypePriceKeys[index]]['id']);
2872			}
2873		}
2874
2875		// otherwise, find the correct field based on ticket type id, and increment it's quantity (for use on ticket types that don't have more than one price)
2876	} else {
2877		quantityFieldId = get_quantity_field_id(ticketTypeCategories[seat['category']['key']]['ticket_type_id'], null);
2878	}
2879	return quantityFieldId;
2880}
2881
2882
2883function seatSelected(seat, selectedTicketType, formId)
2884{
2885	var quantityField = getQuantityFieldOfSelectedSeat(seat, selectedTicketType);
2886
2887	incrementFieldValue(quantityField);
2888	var seatIdsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatIds';
2889	var seatsLabelsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatLabels';
2890	var seatParentField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatParents';
2891	var objTypeField = 'Ticket' + quantityField.match(/\d+/)[0] + 'ObjectType';
2892	var qtyField = 'Ticket' + quantityField.match(/\d+/)[0] + 'Quantity'
2893
2894	$('#'+seatIdsField).val(customTrim($('#'+seatIdsField).val() + "," + seat['uuid'], ",")); // adds the seat id to the data[Ticket][x][seat_ids] field value
2895	$('#'+seatsLabelsField).val(customTrim($('#'+seatsLabelsField).val() + "," + seat['label'], ",")); // adds the seat id to the data[Ticket][x][seat_ids] field value
2896	if (typeof seat['parent'] != 'undefined') { //have to check step by step to avoid javascript error (can't combine in one if statement)
2897		if (typeof seat['parent']['type'] != 'undefined') {
2898			$('#'+seatParentField).val(customTrim($('#'+seatParentField).val() + "," + seat['parent']['type'], ","));
2899			$('#OrderSelectedSeatParents').val(customTrim($('#OrderSelectedSeatParents').val() + "," + seat['parent']['type'], ","));
2900		}
2901	}
2902	$('#OrderSelectedSeatLabels').val(customTrim($('#OrderSelectedSeatLabels').val() + "," + seat['label'], ",")); // adds the seat id to the data[Order][selected_seat_labels] field value
2903	$('#'+objTypeField).val(seat['objectType']);
2904
2905	var transType = $('#OrderPurchaseLocation').val();
2906	if(transType == 'exchange'){
2907		$('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketId').val($('#OrderPurchasedTicketId').val());
2908		$('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketTicketTypeId').val($('#OrderPurchasedTicketTicketTypeId').val());
2909		$('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketTicketTypePriceId').val($('#OrderPurchasedTicketTicketTypePriceId').val());
2910		$('#Ticket' + quantityField.match(/\d+/) + 'PurchasedTicketPrice').val($('#OrderPurchasedTicketPrice').val());
2911	}
2912
2913	calculate_order_total(formId);
2914
2915	checkGratuity('#'+qtyField);
2916
2917}
2918function seatDeselected(seat, selectedTicketType, formId)
2919{
2920	var quantityField = getQuantityFieldOfSelectedSeat(seat, selectedTicketType);
2921	decrementFieldValue(quantityField);
2922	var seatIdsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatIds';
2923	var seatsLabelsField = 'Ticket' + quantityField.match(/\d+/)[0] + 'SeatLabels';
2924	$('#'+seatIdsField).val(customTrim($('#'+seatIdsField).val().replace(seat['uuid'], ''), ",").replace(",,", ",")); // removes the seat id from the data[Ticket][x][seat_ids] field value
2925	$('#'+seatsLabelsField).val(customTrim($('#'+seatsLabelsField).val().replace(seat['label'], ''), ",").replace(",,", ",")); // removes the seat label from the data[Ticket][x][seat_labels] field value
2926	$('#OrderSelectedSeatLabels').val(customTrim($('#OrderSelectedSeatLabels').val().replace(seat['label'], ''), ",").replace(",,", ",")); // removes the seat id from the data[Order][selected_seat_labels] field value
2927	calculate_order_total(formId);
2928	checkGratuity('#'+quantityField);
2929}
2930
2931
2932var selectedSeatsBooked = '';
2933var selectedSeatsBookedTimeout;
2934function selectedSeatBooked(seat)
2935{
2936	//fallback if translated strings are not in memory - if one isn't, neither will be
2937	if (typeof seatsBooked1 == 'undefined') {
2938		var seatsBooked1 = "We're sorry, but one or more of your selected seats (";
2939		var seatsBooked2 = ") have been booked and have been removed from your selection.";
2940	}
2941	if (allowSeatsBookedWarning)
2942	{
2943		clearTimeout(selectedSeatsBookedTimeout);
2944		selectedSeatsBooked += seat.label + ', ';
2945		selectedSeatsBookedTimeout = setTimeout(function() { // does this to allow just one alert instead of more than one per seat
2946			if(selectedSeatsBooked.length > 0) {
2947				alertify.alert("Notice", seatsBooked1 + customTrim(selectedSeatsBooked.trim(), ",") + seatsBooked2).set('modal', true).set('basic', false).pin();
2948				selectedSeatsBooked = '';
2949				calculate_order_total();
2950			}
2951		}, 1000);
2952	}
2953}
2954
2955
2956// trims a specific character from the beginning and end of a string
2957function customTrim(str, chr)
2958{
2959	var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
2960	return str.replace(rgxtrim, '');
2961}
2962
2963
2964
2965// increments the value of an input field
2966function incrementFieldValue(fieldId)
2967{
2968	$("#" + fieldId).val(parseInt($("#" + fieldId).val()) + 1);
2969}
2970
2971// decrements the value of an input field
2972function decrementFieldValue(fieldId)
2973{
2974	$("#" + fieldId).val(parseInt($("#" + fieldId).val()) - 1);
2975}
2976
2977// gets the id of the quantity field based on either a ticketTypeId or a ticketTypePriceId
2978// if a ticket type only has one price, using the ticketTypeId is fine, but if it has more than one, you
2979// need to pass null for the ticketTypeId, then pass the actual ticketTypePriceId
2980function get_quantity_field_id(ticketTypeId, ticketTypePriceId) {
2981	var quantityField = null;
2982
2983	if (typeof isExchange !== 'undefined') {
2984		$("[name^='data[Ticket]'][name$='[ticket_type_id]']").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId;
2985			if(this.value == ticketTypeId && $(this).attr('data-actual-seat')) {
2986				// var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']")
2987				quantityField = $(this).attr('id').replace("TicketTypeId", "Quantity");
2988				return true;
2989			}
2990		});
2991	} else {
2992		if (ticketTypeId != null) {
2993			$("[name^='data[Ticket]'][name$='[ticket_type_id]']").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId;
2994				if(this.value == ticketTypeId) {
2995					// var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']")
2996					quantityField = $(this).attr('id').replace("TicketTypeId", "Quantity");
2997					return true;
2998				}
2999			});
3000		} else if (ticketTypePriceId != null) {
3001			$("[name$='[ticket_type_price_id]']:not([id^='QuestionResponse'])").filter(function () { // looking for field ending with [ticket_type_id] that has a value of the passed ticketTypePriceId and has an ID that does not start with QuestionResponse;
3002				if(this.value == ticketTypePriceId) {
3003					// var id = $(this).attr('id'); //siblings("[name$='[ticket_type_price_id]']")
3004					quantityField = $(this).attr('id').replace("TicketTypePriceId", "Quantity");
3005					return true;
3006				}
3007			});
3008		}
3009	}
3010
3011	return quantityField;
3012}
3013
3014
3015
3016/**
3017 * Seemingly simple function to show the seating chart, but
3018 * have to use visibility instead of display so seats.io chart can render.
3019 * @param {string} categoryKey - seats.io category key used to filter and zoom the chart
3020 * @return {void}
3021 */
3022function showAndGoToSeatingChart(categoryKey) {
3023
3024	if (categoryKey) {
3025		chart.changeConfig({
3026			filteredCategories: [categoryKey]
3027		});
3028		chart.zoomToFilteredCategories();
3029	}
3030
3031	var element = $('#seatsContentArea'),
3032		innerContent = $('#seatsContentArea .content'),
3033		currentHeight = element.height(),
3034		autoHeight = element.css('height', 'auto').height();
3035
3036	$('#seatsContentArea').slideDown('fast', function() {
3037		$('#seatsContentArea .content').slideDown('fast', function() {
3038			$('html,body').animate({scrollTop: $("a[name='seating_chart_area']").offset().top},'normal');
3039		});
3040
3041	});
3042
3043	element.css('visibility', 'visible');
3044	element.css('opacity', '100');
3045}
3046
3047
3048function showItemDetails(item, extra){
3049	//fallback in case translated strings are not in memory
3050	if (typeof hideAdditional == 'undefined') {
3051		var hideAdditional = 'Hide Additional Info';
3052		var showAdditional = 'Show Additional Info';
3053	}
3054
3055	if ($('#less-info-' + item).length) {
3056		//toggle short vs. long description
3057		$('#less-info-' + item).toggle(200);
3058		$('#more-info-' + item).toggle(200);
3059	} else {
3060		//toggle entire details area
3061		$("#item-details-" + item).toggle(200);
3062	}
3063	if (extra) {
3064		//we have to search for the string within the value rather than just
3065		//doing a simple == because it fails under some circumstances otherwise
3066		var info = $('#more-less-'+item).text();
3067		if (info.indexOf('Show Additional Info') > -1) {
3068			$('#more-less-'+item).text(hideAdditional);
3069		} else {
3070			$('#more-less-'+item).text(showAdditional);
3071		}
3072	}
3073}
3074
3075/**
3076 * [editTicketDetails description]
3077 * @param  {[type]} dir [1=open edit | 0=close edit]
3078 * @return {[type]}     [description]
3079 */
3080function editTicketDetails(dir){
3081	if(dir == 1){
3082		// SHOW EDIT FIELDS - HIDE STANDARD DISPLAY
3083		$('#attendee-edit-details').slideDown('fast');
3084		$('#attendee-details').slideUp('fast');
3085	} else {
3086		// HIDE EDIT FIELDS - SHOW STANDARD DISPLAY
3087		$('#attendee-edit-details').slideUp('fast');
3088		$('#attendee-details').slideDown('fast');
3089	}
3090}
3091
3092function switchPaymentMethod(currentMethod){
3093	// simple switch, takes current payment method, and switches to the other one when called
3094	// so it if is currently set at iDEAL, and this function is called, the function is
3095	// to change the form for credit card submission, and vice versa
3096	switch(currentMethod){
3097		case 'ideal': // needs to switch to CC - including enable CC fields
3098			$('#OrderPaymentType').val('creditCard');
3099
3100			// hide ideal, show CC
3101			$('.paymentOptionsCC').slideDown();
3102			$('.paymentOptionsiDEAL').slideUp();
3103
3104			// enable CC fields
3105			if(paymentGateway == 'authnet') {
3106				$('#OrderCcNumber').removeAttr('disabled');
3107				$('#OrderCcExpireMonth').removeAttr('disabled');
3108				$('#OrderCcExpireYear').removeAttr('disabled');
3109				$('#OrderCcCvv').removeAttr('disabled');
3110			}
3111
3112			break;
3113		case 'creditCard': // needs to switch to ideal - including disable CC fields
3114			$('#OrderPaymentType').val('iDEAL');
3115
3116			// hide CC show ideal
3117			$('.paymentOptionsCC').slideUp();
3118			$('.paymentOptionsiDEAL').slideDown();
3119
3120			// disable CC fields
3121			if(paymentGateway == 'authnet') {
3122				$('#OrderCcNumber').attr('disabled', 'disabled');
3123				$('#OrderCcExpireMonth').attr('disabled', 'disabled');
3124				$('#OrderCcExpireYear').attr('disabled', 'disabled');
3125				$('#OrderCcCvv').attr('disabled', 'disabled');
3126			}
3127
3128			break;
3129	}
3130}
3131
3132var currentlyRemovingFromCart = [];
3133
3134function removeFromCart(buttonObject, ttPriceId, formId, aittId) {
3135
3136	id = $('[data-ticket-type-price-id="' + ttPriceId + '"]').attr('id');
3137
3138	var rowIdString = ttPriceId + formId + aittId;
3139
3140	if (currentlyRemovingFromCart.indexOf(rowIdString) === -1) {
3141
3142		buttonObject.fadeTo(0, 0.3);
3143
3144		currentlyRemovingFromCart.push(rowIdString);
3145
3146		// need to remove it from the session data as well
3147		$.ajax({
3148			type: "POST",
3149			url: buildURL("/orders/remove_from_cart/" + ttPriceId + '/' + aittId),
3150			dataType: "json",
3151			success: function (data) {
3152				// if we need anything for success
3153				switch(data['status']){
3154					case 'success':
3155						alertify.success("Successfully removed from shopping cart.");
3156						switch(tsTheme){
3157							case 'Modern':
3158								if ($('#OrderPurchaseLocation').val() == 'exchange') {
3159									if ($('#' + id).siblings('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId || aittId == 'undefined') {
3160										$('#' + id).val('');
3161									}
3162
3163									// ticket exchanges
3164									// removes from previous selections
3165									$('#ticket-' + ttPriceId).empty();
3166
3167									// removes from current selections
3168									$('#selected-ticket-' + ttPriceId).find('.selectButton').trigger("click");
3169								} else {
3170									if ($('#' + id).closest('.ticketOptions').find('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId) {
3171										$('#' + id).val('0').prev('a').addClass('disabled');
3172									}
3173									$('input[data-ticket-type-price-id=' + ttPriceId + ']').val(0);
3174								}
3175
3176								$('#ticket-selection-container-' + aittId).empty();
3177								$('#ticket-selection-container-' + ttPriceId).empty();
3178
3179								break;
3180							case 'Classic':
3181							default:
3182								if ($('#' + id).siblings('[data-field-name="activity_instance_ticket_type_id"]').val() == aittId || aittId == 'undefined') {
3183									$('#' + id).val('');
3184								}
3185								$('#ticket-selection-container-' + aittId).empty();
3186								$('#ticket-selection-container-' + ttPriceId).empty();
3187
3188								//ticket exchanges
3189								//--removes from previous selections
3190								$('#ticket-' + ttPriceId).empty();
3191
3192								//--removes from current selections
3193								$('#selected-ticket-' + ttPriceId).find('.selectButton').trigger("click");
3194								break;
3195						}
3196						// this is where I want do it, need to get the element of the quantity selector for this one.
3197						var thisElement = $('#' + id);
3198						//set the quantity explicitly to 0 (previously was leaving it blank, which was causing notices under some circumstances)
3199						thisElement.val('0');
3200						checkGratuity(thisElement);
3201						calculate_order_total(formId);
3202						setTimeout(removeFromRemovingFromCartArray, 3000, rowIdString);
3203						break;
3204					default:
3205
3206						// if not successful, remove from "currentlyRemovingFromCart" array so it will allow clicking again
3207						removeFromRemovingFromCartArray(rowIdString);
3208						buttonObject.fadeTo("fast", 1);
3209
3210						alertify.error("Could not be removed from shopping cart.");
3211						return false;
3212						break;
3213				}
3214			},
3215			error: function (errorMessage) {
3216
3217				// if errored, remove from "currentlyRemovingFromCart" array so it will allow clicking again
3218				removeFromRemovingFromCartArray(rowIdString);
3219				buttonObject.fadeTo(0, 1);
3220
3221				// if we need anything from error
3222				return false;
3223			},
3224			complete: function(data) {
3225				$('#mainTicketSelection').removeClass('hidden');
3226			}
3227		});
3228	}
3229}
3230
3231function removeFromRemovingFromCartArray(rowIdString) {
3232	let index = currentlyRemovingFromCart.indexOf(rowIdString);
3233	if (index > -1) {
3234	  currentlyRemovingFromCart.splice(index, 1);
3235	}
3236}
3237
3238function removeFromCartGiftCard(giftCard, formId) {
3239	switch(tsTheme){
3240		case 'Modern':
3241			$('[name="data[CreditProduct][' + giftCard + '][quantity]"]').val('0');
3242			break;
3243		case 'Classic':
3244		default:
3245			$('[name="data[CreditProduct][' + giftCard + '][quantity]"]').val('');
3246			break;
3247	}
3248	calculate_order_total(formId);
3249}
3250
3251function removeCharityFromCart(charityId, formId) {
3252	name = $('[value="' + charityId + '"]').attr('name');
3253	name = name.replace('charity_event_id', 'total');
3254	$('[name="' + name + '"]').val('');
3255	calculate_order_total(formId);
3256}
3257
3258function disableButton(ticketId, soldOutText){
3259	$('#showButton-' + ticketId).removeClass('btn');
3260	$('#showButton-' + ticketId).addClass('ticket-sold-out-text');
3261	$('#showButton-' + ticketId).removeAttr('href');
3262	$('#showButton-' + ticketId).removeAttr('style');
3263	$('#showButton-' + ticketId).html(soldOutText);
3264}
3265
3266var interval; //we need this available globally so we can cancel a timer in progress if necessary
3267function timerCountdown(endTime, controlId) {
3268	var minutes, seconds;
3269	endTime = new Date(endTime).getTime();
3270
3271	if (isNaN(endTime)) {
3272		return false;
3273	}
3274
3275	interval = setInterval(timerCalculate, 1000);
3276
3277	function timerCalculate() {
3278		//get right now
3279		var startTime = new Date();
3280
3281		//convert right now to UTC
3282		startTime = new Date(startTime.getTime() + startTime.getTimezoneOffset() * 60000);
3283
3284		//get time remaining
3285		var timeRemaining = parseInt((endTime - startTime) / 1000);
3286
3287		if (timeRemaining < 0) {
3288			timeRemaining = 0;
3289		}
3290
3291		minutes = parseInt(timeRemaining / 60);
3292		timeRemaining = (timeRemaining % 60);
3293		seconds = parseInt(timeRemaining);
3294
3295		//are we all done or not?
3296		var allDone = false;
3297		if (minutes == 0 && seconds == 0) {
3298			allDone = true;
3299		}
3300
3301		//put a leading 0 on minutes if necessary
3302		if (minutes.toString().length == 1) {
3303			minutes = '0' + minutes.toString();
3304		}
3305
3306		//put a leading 0 on seconds if necessary
3307		if (seconds.toString().length == 1) {
3308			seconds = '0' + seconds.toString();
3309		}
3310
3311		//put the time into the container's minutes child class
3312		$('#' + controlId + ' .timer-minutes').text(minutes + ":" + seconds);
3313
3314		//if the countdown has reached 0, we need to clear it out then reload the page after a couple of seconds
3315		if (allDone) {
3316			//add the time's up message
3317			$('#' + controlId + ' .timer-message').text("Sorry, time's up!");
3318
3319			//make the calculations stop
3320			clearInterval(interval);
3321
3322			//use an ajax call to clear out the prefill
3323			$.ajax({
3324				type: "GET",
3325				url: buildURL("/admin/orders/ajax_clear_prefill/"),
3326				dataType: "json",
3327				success: function (data) {
3328					ignoreLeaveAttempts();
3329					//need to get the window to reload WITHOUT previous POST data
3330					window.location.href = window.location.href;
3331				},
3332				error: function (errorMessage) {
3333					return false;
3334				}
3335			});
3336		}
3337	}
3338}
3339
3340/**
3341 * Finds the highest data[Ticket][n] index already on the page.
3342 * The page-level ticketRowKey is printed before the ticket rows finish rendering, so it can
3343 * be lower than the indexes the server already used. Reading the form keeps appended rows
3344 * from re-using an index and overwriting another instance's selection on submit.
3345 */
3346function findHighestTicketRowKey() {
3347	let highestRowKey = -1;
3348
3349	$('[name^="data[Ticket]["]').each(function () {
3350		const rowKeyMatch = this.name.match(/^data\[Ticket\]\[(\d+)\]/);
3351		if (rowKeyMatch) {
3352			highestRowKey = Math.max(highestRowKey, parseInt(rowKeyMatch[1], 10));
3353		}
3354	});
3355
3356	return highestRowKey;
3357}
3358
3359function processSelectedTickets(selections){
3360	ticketRowKey = Math.max(ticketRowKey, findHighestTicketRowKey());
3361
3362	selections.forEach(function(selection){
3363		if(selection.quantity > 0){
3364			ticketRowKey++;
3365			//create the container
3366			if(selection.activity_instance_ticket_type_id) {
3367				var containerID = 'ticket-selection-container-'  + selection.activity_instance_ticket_type_id;
3368			} else {
3369				var containerID = 'ticket-selection-container-'  + selection.ticket_type_price_id;
3370			}
3371
3372			var newSelection = '<div id="'  + containerID + '" class="ticketOptions"></div>';
3373			$('#ticketSelected').append(newSelection);
3374
3375			//create fields
3376			var formFields = '';
3377			formFields += '<div id="selected_' + selection.activity_instance_ticket_type_id + '">';
3378			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_price_name]" value="' + selection.ticket_type_price_name + '" id="Ticket' + ticketRowKey + 'TicketTypePriceName">';
3379			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_name]" value="' + selection.ticket_type_name + '" id="Ticket' + ticketRowKey + 'TicketTypeName">';
3380			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][price_each]" value="' + selection.price_each + '" id="Ticket' + ticketRowKey + 'PriceEach">';
3381			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_price_id]" value="' + selection.ticket_type_price_id + '" id="Ticket' + ticketRowKey + 'TicketTypePriceId">';
3382			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][ticket_type_id]" value="' + selection.ticket_type_id + '" id="Ticket' + ticketRowKey + 'TicketTypeId">';
3383			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_ticket_type_id]" value="' + selection.activity_instance_ticket_type_id + '" id="Ticket' + ticketRowKey + 'ActivityInstanceTicketTypeId">';
3384			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_id]" value="' + selection.activity_instance_id + '" id="Ticket' + ticketRowKey + 'ActivityInstanceId">';
3385			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_id]" value="' + selection.activity_id + '" id="Ticket' + ticketRowKey + 'ActivityId">';
3386			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][auto_register]" value="' + selection.auto_register + '" id="Ticket' + ticketRowKey + 'AutoRegister">';
3387			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][selective_register]" value="' + selection.selective_register + '" id="Ticket' + ticketRowKey + 'SelectiveRegister">';
3388			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_name]" value="' + selection.activity_name + '" id="Ticket' + ticketRowKey + 'ActivityName">';
3389			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_instance_date_time]" value="' + selection.activity_instance_date_time + '" id="Ticket' + ticketRowKey + 'ActivityInstanceDateTime">';
3390			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][hold]" value="holding" id="Ticket' + ticketRowKey + 'Hold">';
3391			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_start_time]" value="' + selection.activity_start_time + '" id="Ticket' + ticketRowKey + 'ActivityStartTime">';
3392			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][activity_end_time]" value="' + selection.activity_end_time + '" id="Ticket' + ticketRowKey + 'ActivityEndTime">';
3393			formFields += '<input type="hidden" name="data[Ticket][' + ticketRowKey + '][quantity]" value="' + selection.quantity + '" id="Ticket' + ticketRowKey + 'Quantity">';
3394			formFields += '</div>';
3395
3396			$('#' + containerID).append(formFields);
3397		}
3398	});
3399}
3400
3401/**
3402 *  Gets `orderPrefillData`
3403 */
3404function checkForCartItems(){
3405	$.ajax({
3406		type: "POST",
3407		url: buildURL('/orders/ajax_get_prefill_data/'),
3408		data: $('#orderForm').serialize(),
3409		dataType: "json",
3410		success: function(response) {
3411			switch(response['status']) {
3412				case 'success':
3413					updateConfirmation(response);
3414					break;
3415
3416				case 'error':
3417				case 'fail':
3418				default:
3419
3420					break;
3421			}
3422		},
3423
3424	});
3425}
3426
3427/**
3428 * Resets all exchange buttons to their default "Exchange" state
3429 */
3430function resetExchangeButtons() {
3431	$('.exchangeSingleTicket').each(function() {
3432		var button = $(this);
3433		var ticketId = button.data('ticket-id');
3434
3435		// Skip if this is not a valid exchange button or if it's disabled
3436		if (!ticketId || button.hasClass('btn-disabled')) {
3437			return;
3438		}
3439
3440		if (button.hasClass('editExchangeButton')) {
3441			button.removeClass('editExchangeButton');
3442			var productName = button.data('product-name') || 'Ticket';
3443			var icon = '<i class="fa-solid fa-rotate" aria-hidden="true"></i>';
3444			button.html('Exchange ' + productName + icon);
3445		}
3446	});
3447}
3448
3449/**
3450 * Sets a specific exchange button to "Edit Exchange" state
3451 * @param {string} ticketId - The ticket ID
3452 */
3453function setExchangeButtonToEditExchange(ticketId) {
3454	var $button = $('.exchangeSingleTicket[data-ticket-id="' + ticketId + '"]');
3455	if ($button.length && !$button.hasClass('btn-disabled')) {
3456		$button.addClass('editExchangeButton');
3457		$button.html('Edit Exchange<i class="fa-solid fa-rotate" aria-hidden="true"></i>');
3458	}
3459}
3460
3461function updateConfirmation(response){
3462	var selectionCount = 0;
3463	var isModern = tsTheme === 'Modern';
3464	if (isModern) {
3465		$('#manage-attendees').find('.exchange-info-row').hide();
3466		$('#manage-attendees').find('.exchangeLabel').empty();
3467	} else {
3468		$('.attendee-row').removeClass('ticketExchangePending');
3469		$('.attendee-row').find('.exchangeInfo').empty();
3470	}
3471
3472	// Reset all exchange buttons to default "Exchange" state
3473	resetExchangeButtons();
3474
3475	$.each(response, function(key, value){
3476		if(key != 'Order' && key != 'status' && key != 'Timer'){
3477			if(value['seat_labels'] != ''){
3478				exchangeLabel = value['seat_labels'];
3479			} else {
3480				exchangeLabel = value['ticket_type_name'];
3481			}
3482			if (isModern) {
3483				if (value['activity_instance_date_time'].length > 0) {
3484					exchangeLabel += '<br><i class="fa fa-calendar-alt" aria-hidden="true"></i> ' + value['activity_instance_date_time'];
3485				}
3486				$('#exchange-info-' + key + ' .exchangeLabel').html(exchangeLabel);
3487				$('#exchange-info-row-' + key).show();
3488
3489				// Update the button for this ticket to "Edit Exchange"
3490				setExchangeButtonToEditExchange(key);
3491			} else {
3492				$('#attendee-row-' + key).addClass('ticketExchangePending');
3493				if (value['activity_instance_date_time'].length > 0) {
3494					exchangeLabel += ' - ' + value['activity_instance_date_time'];
3495				}
3496				$('#attendee-row-' + key).find('.exchangeInfo').html('Pending Exchange to: ' + exchangeLabel);
3497			}
3498			selectionCount++;
3499		}
3500	});
3501
3502	if(selectionCount > 0){
3503		if (isModern) {
3504			$('#tickets-summary-footer').show();
3505		}
3506		$('#exchangeCheckoutButton').show();
3507	} else {
3508		if (isModern) {
3509			$('#tickets-summary-footer').hide();
3510		}
3511		$('#exchangeCheckoutButton').hide();
3512	}
3513}
3514
3515var timeoutVar;
3516function process_popup(qty, id) {
3517	clearTimeout(timeoutVar);
3518	if (qty > 0) {
3519		timeoutVar = setTimeout(function(){
3520			$('#cart-popup-trigger-' + id).trigger("click"); //triggers the fancybox
3521		}, 500);
3522	}
3523}
3524
3525/**
3526 *
3527 * function calculates and displays the options for gratuity on the tickets page.
3528 */
3529function checkGratuity(ele){
3530	var selectedValue = 0;
3531	var priceEach = 0;
3532	var totalPrice = 0;
3533	var allPrices = [];
3534	if($(ele).data('has-active-gratuity')){
3535		// get ticket type id\
3536		ticketTypeId = $(ele).data('ticket-type-id');
3537		// go through each of the options for this ticket type on the page and compile the data
3538		$('[data-ticket-type-id=' + ticketTypeId + '][data-field=quantity]').each(function(ticketTypeId){
3539			if($(this).data('has-active-gratuity')){
3540				// set for this selection
3541				thisSelectedValue = parseInt($(this).val(), 10);
3542				if(thisSelectedValue > 0){
3543					thisPriceEach = $(this).data('price-each');
3544					thisTotalPrice = thisPriceEach * thisSelectedValue;
3545
3546					// add them up
3547					selectedValue = selectedValue + thisSelectedValue;
3548					totalPrice = totalPrice + thisTotalPrice;
3549
3550					// get array of prices
3551					allPrices.push(thisPriceEach);
3552				}
3553			}
3554		});
3555
3556		if(selectedValue > 0){
3557			// if selected value is greater than 0 then display the gratuity area for this ticket type
3558			$('.selected-amount-' + ticketTypeId).html(selectedValue);
3559			$('#gratuity-' + ticketTypeId + ' .indGratuityOption').each(function(){
3560				var value = $(this).data('order');
3561				var gratuityTotal = totalPrice * value / 100;
3562				$(this).find('.gratuity-option-preview').html('$' + gratuityTotal.toFixed(2));
3563			});
3564
3565			if(allPrices.length == 1){
3566				// single ticket type (price) being selected
3567				var displayTotal = parseFloat(allPrices[0], 2);
3568				$('#selected-total-' + ticketTypeId).html('($' + displayTotal.toFixed(2) + ')');
3569			} else {
3570				// multiple ticket type (price) selected
3571				var min = Math.min.apply(Math, allPrices);
3572				var max = Math.max.apply(Math,allPrices);
3573				$('#selected-total-' + ticketTypeId).html('@ ($' + min.toFixed(2) + ' - $' + max.toFixed(2) + ')');
3574			}
3575			// show gratuity area for this ticket type
3576			$('#gratuities').fadeIn();
3577			$('#gratuity-content').fadeIn();
3578			$('#gratuity-' + ticketTypeId).fadeIn();
3579			$('#Gratuity' + ticketTypeId + 'Value').removeAttr('disabled');
3580		} else {
3581			// if selected value is 0 clear and hide gratuity area for this ticket type.
3582			$('#gratuity-' + ticketTypeId).fadeOut();
3583			$('.selected-amount-' + ticketTypeId).html(0);
3584			$('.selected-amount-' + ticketTypeId).closest('gratuity-option-preview').html(0);
3585			$('#Gratuity' + ticketTypeId + 'Value').attr('disabled', 'disabled');
3586		}
3587	}
3588	return true;
3589}
3590
3591function clearOutSeatedFields() {
3592	if (typeof renderSeatingChart === 'function') {
3593		$('input[name="data[Order][selected_seat_labels]"]').val('');
3594		$('input[name="data[Order_selected_seats]"]').val('');
3595		$('input[name="data[Order][selected_seat_parents]"]').val('');
3596		$('input[name$="][seat_labels]"]').val('');
3597		$('input[name$="][seat_ids]"]').val('');
3598		$('input[name$="][object_type]"]').val('');
3599		$('input[name$="][seat_parents]"]').val('');
3600		$('input[name$="][seat_labels]"]').parent().find('input[name$="][quantity]"]').val('0');
3601    }
3602}
3603
3604function toggleTicketTypeGroupVisibility(ticketTypeGroupId) {
3605	let elem = $('#ticketTypeGroup_' + ticketTypeGroupId + ' .ticketTypeGroupContent');
3606	if (elem.is(":visible")) {
3607		elem.slideUp('fast');
3608		$('#ticketTypeGroup_' + ticketTypeGroupId).addClass('collapsed').removeClass('expanded');
3609		$('#ticketTypeGroup_' + ticketTypeGroupId + ' .group-status').addClass('fa-caret-right').removeClass('fa-caret-down');
3610	} else {
3611		elem.slideDown('fast');
3612		$('#ticketTypeGroup_' + ticketTypeGroupId).addClass('expanded').removeClass('collapsed');
3613		$('#ticketTypeGroup_' + ticketTypeGroupId + ' .group-status').addClass('fa-caret-down').removeClass('fa-caret-right');
3614	}
3615
3616}
3617
3618/**
3619 * Removes DOM elements for tickets that have zero quantity and haven't been modified by users.
3620 *
3621 * This function identifies unmodified zero-quantity tickets and removes all associated
3622 * form elements (inputs and selects) from the DOM to prevent them from being submitted.
3623 *
3624 * @author Brig Evans
3625 */
3626function removeDomElementsForUnmodifiedZeroQuantityTickets() {
3627	var unmodifiedZeroQuantityTicketIndices = getZeroQuantityUnmodifiedTicketIndices();
3628
3629	if (unmodifiedZeroQuantityTicketIndices.length > 0) {
3630		unmodifiedZeroQuantityTicketIndices.forEach(function (ticketIndexToRemove) {
3631			var ticketFormElementsSelector = 'input[name*="[Ticket][' + ticketIndexToRemove + ']"], select[name*="[Ticket][' + ticketIndexToRemove + ']"]';
3632			$(ticketFormElementsSelector).remove();
3633		});
3634	}
3635}
3636
3637/**
3638 * Retrieves form data with unmodified zero-quantity ticket fields excluded.
3639 *
3640 * Serializes the specified form, identifies ticket fields that should be excluded
3641 * (zero quantity tickets that haven't been modified by users), filters them out,
3642 * and returns the remaining data as a URL-encoded parameter string suitable for submission.
3643 *
3644 * @param string formId The jQuery selector for the form to process
3645 * @return string URL-encoded form data with excluded ticket fields removed
3646 * @author Brig Evans
3647 */
3648function getOrderFormDataWithoutUnmodifiedZeroQuantityTickets(formId) {
3649	var unmodifiedZeroQuantityTicketIndices = getZeroQuantityUnmodifiedTicketIndices();
3650	var allSerializedFormData = $(formId).serializeArray();
3651
3652	var formDataWithoutExcludedTickets = allSerializedFormData.filter(function (currentFormField) {
3653		var shouldExcludeCurrentField = false;
3654
3655		// Check if current field belongs to any excluded ticket index
3656		for (var currentExcludedIndex = 0; currentExcludedIndex < unmodifiedZeroQuantityTicketIndices.length; currentExcludedIndex++) {
3657			var ticketIndexPattern = '[Ticket][' + unmodifiedZeroQuantityTicketIndices[currentExcludedIndex] + ']';
3658
3659			if (currentFormField.name.includes(ticketIndexPattern)) {
3660				shouldExcludeCurrentField = true;
3661				break; // Found match, no need to continue checking
3662			}
3663		}
3664
3665		return !shouldExcludeCurrentField; // Include field if it shouldn't be excluded
3666	});
3667
3668	return $.param(formDataWithoutExcludedTickets);
3669}
3670
3671/**
3672 * Identifies ticket type indices that have zero quantity and haven't been modified by user interaction.
3673 *
3674 * Scans all ticket quantity select elements to find those with quantity 0 that don't have
3675 * the data-exists-in-prefill attribute, indicating they haven't been interacted with.
3676 *
3677 * @return array Array of ticket indices (strings) representing unselected ticket types
3678 * @author Brig Evans
3679 */
3680function getZeroQuantityUnmodifiedTicketIndices() {
3681	var unmodifiedZeroQuantityTicketIndices = [];
3682
3683	$('select[name*="[Ticket]"][name*="[quantity]"]').each(function () {
3684		var currentTicketQuantitySelect = $(this);
3685		var selectedTicketQuantity = parseInt(currentTicketQuantitySelect.val()) || 0;
3686		var ticketIndexExtractionMatch = currentTicketQuantitySelect.attr('name').match(/\[Ticket\]\[(\d+)\]/);
3687
3688		if (!ticketIndexExtractionMatch) {
3689			return true; // Continue iteration - no valid ticket index found
3690		}
3691
3692		var extractedTicketIndex = ticketIndexExtractionMatch[1];
3693		var hasBeenModifiedByUser = currentTicketQuantitySelect.attr('data-exists-in-prefill');
3694
3695		// Include ticket index only if quantity is zero AND user hasn't modified it
3696		if (selectedTicketQuantity === 0 && !hasBeenModifiedByUser) {
3697			unmodifiedZeroQuantityTicketIndices.push(extractedTicketIndex);
3698		}
3699	});
3700
3701	// Dropdown-view and seating ticket types store quantity in a hidden input (not a select).
3702	// Users never interact with these inputs directly — they're set programmatically via the
3703	// "Add >" button or seating chart — so value 0 always means the ticket wasn't selected.
3704	$('input[type="hidden"][name*="[Ticket]"][name*="[quantity]"]').each(function () {
3705		const hiddenInput = $(this);
3706		const hiddenQuantity = parseInt(hiddenInput.val()) || 0;
3707		const hiddenIndexMatch = hiddenInput.attr('name').match(/\[Ticket\]\[(\d+)\]/);
3708
3709		if (!hiddenIndexMatch) {
3710			return true; // Continue iteration - no valid ticket index found
3711		}
3712
3713		if (hiddenQuantity === 0) {
3714			unmodifiedZeroQuantityTicketIndices.push(hiddenIndexMatch[1]);
3715		}
3716	});
3717
3718	return unmodifiedZeroQuantityTicketIndices;
3719}
3720
3721/**
3722 * Initializes tracking system for ticket quantity interactions to distinguish between
3723 * pre-filled quantities and user modifications.
3724 *
3725 * Sets up initial state by marking tickets with non-zero quantities as pre-filled,
3726 * then establishes event handlers to track when users interact with quantity selectors.
3727 * This tracking prevents removal of tickets that users have explicitly set to zero.
3728 *
3729 * @author Brig Evans
3730 */
3731function initializeTicketQuantityInteractionTracking() {
3732	// Mark tickets with initial non-zero quantities as pre-filled to prevent removal
3733	$('select[name*="[Ticket]"][name*="[quantity]"]').each(function () {
3734		var currentTicketQuantitySelect = $(this);
3735		var initialTicketQuantity = parseInt(currentTicketQuantitySelect.val()) || 0;
3736
3737		// Mark as pre-filled only if initial quantity is greater than zero
3738		if (initialTicketQuantity > 0) {
3739			currentTicketQuantitySelect.attr('data-exists-in-prefill', 1);
3740		}
3741	});
3742
3743	// Establish event handler to mark quantity selectors as modified when user changes them
3744	$(document).on('change', 'select[name*="[Ticket]"][name*="[quantity]"]', function () {
3745		var changedTicketQuantitySelect = $(this);
3746
3747		// Mark this ticket as having been interacted with to prevent future removal
3748		changedTicketQuantitySelect.attr('data-exists-in-prefill', 1);
3749	});
3750}

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.