PageSourceSearch

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

js volumeonetickets.org collected 2026-09-25 14:26:04 UTC 136,174 bytes, 3,741 lines download raw bytes

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

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.