PageSourceSearch

https://orders.completecateringco.com.au/themes/frontend/base/js/checkout.js?1789449056

js completecateringco.com.au collected 2026-10-05 22:25:50 UTC 68,033 bytes, 1,751 lines download raw bytes

1var flex = (typeof flex !== 'undefined') ? flex : {};
2
3flex.checkout = (typeof flex.checkout !== 'undefined') ? flex.checkout : {};
4flex.checkout.locationSelect = function($select, callback) {
5    callback = typeof callback !== 'undefined' ? callback : function($el) {};
6    var location = {
7        _perPage: 20,
8        init: function() {
9            var self = this;
10            self.$select = $select;
11            self.$select.select2(self._select2Option('- '+i18n.gettext('Select Location')+' -'));
12            flex.applySelect2Aria(self.$select);
13
14            self.$select.val(self.$select.data('selected')).trigger('change');
15
16            self.$select.on('change', function() {
17                callback($(this));
18            });
19
20            // identification that this was already initiated
21            self.$select.data('flex.checkout.locationSelect', 1);
22        },
23        _select2Option: function(placeHolder) {
24            var self = this;
25            return {
26                placeholder: placeHolder,
27                ajax: {
28                    url: baseUrl + "order/cart/locations",
29                    delay: 250,
30                    dataType: 'json',
31                    data: function (params) { // page is the one-based page number tracked by Select2
32                        $this = $(this);
33                        return {
34                            q: params.term, //search term
35                            page: params.page, // page number
36                            per_page: self._perPage,
37                            for_pickup: 1
38                        };
39                    },
40                    processResults: function (data, params) {
41                        return {
42                            results: data.items,
43                            pagination: {
44                                more: ((params.page ? params.page : 1) * self._perPage) < data.total_count
45                            }
46                        };
47                    }
48                },
49                templateResult: self._formatResult, // omitted for brevity, see the source of this page
50                templateSelection: self._formatSelection, // omitted for brevity, see the source of this page
51                escapeMarkup: function (m) {
52                    return m;
53                }, // we do not want to escape markup since we are displaying html in results
54                language: {
55                    errorLoading: function () {
56                        return i18n.gettext("Searching...");
57                    }
58                }
59            }
60        },
61
62        _formatResult: function (result) {
63            if (result.loading) {
64                return flex.utils.e(result.text);
65            }
66
67            var markup = '<div class="row-fluid">' +
68                '<div class="span12">' +
69                '<div class="row-fluid">';
70            markup += '<div class="span12"> <b>' + flex.utils.e(result.title) + '</b>';
71
72            markup += '';
73            var footerMarkup = '';
74            var address = '';
75            if (result.street) {
76                address += flex.utils.e(result.street);
77            }
78
79            if (result.state) {
80                address += ', ' + flex.utils.e(result.state);
81            }
82
83            if (result.postcode) {
84                address += ', ' + flex.utils.e(result.postcode);
85            }
86
87            if (result.phone) {
88                address += '<br/>P: ' + flex.utils.e(result.phone);
89            }
90
91
92            footerMarkup += '<small class="small">' + address + '</small>';
93
94            markup += '<br />' + footerMarkup;
95
96            markup += '</div></div></div>';
97
98            return markup;
99        },
100
101        _formatSelection: function (item) {
102            return flex.utils.e(item.title ? item.title : item.text);
103        },
104    };
105
106    location.init();
107};
108
109flex.checkout.newLocationSelect = function($modal, showClose = false) {
110    var locationsList = {
111        _perPage: 10,
112        _totalPages: 0,
113        _currentPage: 0,
114        _isLoading: false,
115        init: function() {
116            var self = this;
117            self.xhr = null;
118
119            self.initModal();
120            self.getLocationsList();
121            self.initScrollView();
122            self.updateContinueButton();
123
124            self.getSearchButtonEl().on('click touchend', function (event) {
125                event.preventDefault();
126                self.getLocationsList(self.getSearchFieldEl().val());
127            });
128
129            self.getSearchFieldEl().on('keypress', function (event) {
130                if (event.which === 13) {
131                    event.preventDefault();
132                    self.getLocationsList(self.getSearchFieldEl().val());
133                }
134            });
135
136            self.getMethodFilterEl().on('click keydown', '.list-item', function (e) {
137                if (e.type === 'keydown' && e.which !== 13 && e.which !== 32) {
138                    return;
139                }
140
141                e.preventDefault();
142                self.setMethodFilterActive($(this));
143                self.getLocationsList(self.getSearchFieldEl().val());
144            });
145
146            $modal.on('change', '.js-input-select-location', function () {
147                self.updateContinueButton();
148            });
149
150            self.getContinueButtonEl().on('click touchend', function (event) {
151                event.preventDefault();
152                $(this).closest('form').submit();
153            });
154
155            if (showClose) {
156                $modal.find('.close').show();
157            } else {
158                $modal.find('.close').hide();
159            }
160        },
161        initModal: function () {
162            $modal.modal('show');
163        },
164        getSearchFieldEl: function () {
165            return $modal.find('.js-search-location-field-modal');
166        },
167        getSearchButtonEl: function () {
168            return $modal.find('.js-search-location-modal-btn');
169        },
170        getMethodFilterEl: function () {
171            return $modal.find('.js-method-filter');
172        },
173        getLocationsContainerEl: function () {
174            return $modal.find('.js-location-container');
175        },
176        getLocationSpinnerEl: function () {
177            return $modal.find('.js-location-modal-loader');
178        },
179        getLocationStatusEl: function () {
180            return $modal.find('.js-location-modal-status');
181        },
182        getContinueButtonEl: function () {
183            return $modal.find('.js-submit-select-location');
184        },
185        getButtonContainerEl: function () {
186            return $modal.find('.js-location-btn-container');
187        },
188
189        getMethodFilterValue: function() {
190            return this.getMethodFilterEl().find('.list-item.active').data('value');
191        },
192        setMethodFilterActive: function($item) {
193            var $items = this.getMethodFilterEl().find('.list-item');
194
195            $items.removeClass('active').attr('aria-pressed', 'false');
196            $item.addClass('active').attr('aria-pressed', 'true');
197        },
198        setLoadingState: function(isLoading) {
199            this.getLocationsContainerEl().attr('aria-busy', isLoading ? 'true' : 'false');
200
201            if (isLoading) {
202                this.setLocationStatus(i18n.gettext("Loading locations."));
203            }
204        },
205        setLocationStatus: function(message) {
206            this.getLocationStatusEl().text(message);
207        },
208        updateContinueButton: function() {
209            var hasSelectedLocation = this.getLocationsContainerEl().find('.js-input-select-location:checked').length > 0;
210
211            this.getContinueButtonEl()
212                .prop('disabled', !hasSelectedLocation)
213                .attr('aria-disabled', hasSelectedLocation ? 'false' : 'true');
214        },
215
216        getLocationsList: function (keyword = null, page = 1) {
217            var self = this,
218                spinnerEl = self.getLocationSpinnerEl(),
219                queryData = {};
220
221            var filteredMethod = self.getMethodFilterValue();
222
223            if (keyword != null && keyword != '') {
224                queryData = {
225                    address: keyword
226                };
227            } else {
228                queryData = {page: page, per_page: (filteredMethod? 1000: self._perPage)};
229            }
230
231            if (self._isLoading) {
232                return;
233            }
234
235            if (self._currentPage == 0 || keyword != null) {
236                self.getLocationsContainerEl().empty();
237                self.updateContinueButton();
238            }
239
240            self._isLoading = true;
241            self.setLoadingState(true);
242
243            abortAjax(self.xhr);
244            spinnerEl.show();
245
246            self.xhr = $.ajax({
247                type: 'GET',
248                url: baseUrl + 'order/cart/locations',
249                data: queryData,
250                dataType: 'json',
251                success: function (data) {
252
253                    if ('pagination' in data) {
254                        self._totalPages = data.pagination.total_pages;
255                        self._currentPage = data.pagination.page;
256                    } else {
257                        self._totalPages = 0;
258                        self._currentPage = 0;
259                    }
260
261                    self.formatDataAndDisplay(data);
262                    self.setLoadingState(false);
263                },
264                error: function (xhr, textStatus) {
265                    if (textStatus !== 'abort') {
266                        self.setLoadingState(false);
267                        self.setLocationStatus(i18n.gettext("Unable to load locations."));
268                    }
269                },
270                complete: function () {
271                    self._isLoading = false;
272                    spinnerEl.hide();
273                }
274            });
275        },
276        formatDataAndDisplay: function (data) {
277            var self = this;
278            var filteredMethod = self.getMethodFilterValue();
279
280            let validItems = data.items.filter(function(location) {
281                if (filteredMethod === 'delivery' && !location.is_enable_delivery) return false;
282                if (filteredMethod === 'pickup' && !location.is_enable_pickup) return false;
283                return true;
284            });
285
286            if (validItems.length) {
287                validItems.forEach(function(location) {
288                    let distanceToSearch = '';
289                    if (location.distance_to_search) {
290                        distanceToSearch = `<span class="location-distance mb-2">${flex.utils.e(location.distance_to_search)}</span>`;
291                    }
292
293                    var hasMethod = location.is_enable_delivery || location.is_enable_pickup;
294
295                    let deliveryBadge = location.is_enable_delivery
296                        ? `<div class="btn-badge">${i18n.gettext("Delivery")}</div>`
297                        : "";
298
299                    let pickupBadge = location.is_enable_pickup
300                        ? `<div class="btn-badge">${i18n.gettext("Pick Up")}</div>`
301                        : "";
302
303                    let badges = (deliveryBadge || pickupBadge)
304                        ? `<div class="shipping-method">
305                           ${deliveryBadge}
306                           ${pickupBadge}
307                       </div>`
308                        : "";
309
310                    let locationInputId = 'checkout-location-' + location.id;
311                    let locationTabIndex = hasMethod ? '0' : '-1';
312                    let locationAriaDisabled = hasMethod ? 'false' : 'true';
313                    let locationDisabledAttribute = hasMethod ? '' : 'disabled';
314
315                    let locationItem = `<div class="checkbox-card3 checkbox-card3-border 
315checkbox-card3-location" >
316                        <label class="checkbox-label" tabindex="${locationTabIndex}" role="radio" aria-checked="false" aria-disabled="${locationAriaDisabled}" for="${locationInputId}">
317                            <input
318                                id="${locationInputId}"
319                                value="${location.id}"
320                                name="location_id"
321                                type="radio" class="js-input-select-location"
322                                tabindex="-1"
323                                required
324                                ${locationDisabledAttribute}
325                                >
326                            <div class="checkbox-text">
327                                <div class="checkbox-text-inner">
328                                    <strong class="${!location.distance_to_search ? 'mb-2' : ''}">${flex.utils.e(location.title)}</strong>
329                                    ${distanceToSearch}
330                                    <span>${flex.utils.e(location.address_full_wo_country)}</span>
331                                    <span>${flex.utils.e(location.phone)}</span>
332                                </div>
333                                ${badges}
334                            </div>
335                        </label>
336                    </div>`;
337
338                    self.getLocationsContainerEl().append($(locationItem));
339                });
340
341                self.getButtonContainerEl().show();
342                self.setLocationStatus(i18n.gettext("Locations loaded."));
343            } else {
344                self.getLocationsContainerEl().append(`<span>${i18n.gettext("No matching locations found.")}</span>`);
345                self.getButtonContainerEl().hide();
346                self.setLocationStatus(i18n.gettext("No matching locations found."));
347            }
348
349            self.updateContinueButton();
350        },
351        initScrollView: function () {
352            let self = this;
353            let $container = self.getLocationsContainerEl();
354
355            // Use native element reference for standard dimension calls
356            let containerEl = $container[0];
357
358            $container.on('scroll', function (event) {
359                // DO NOT use event.preventDefault() or event.stopPropagation() here;
360                // it breaks scroll bubbling in legacy Android WebViews.
361
362                // Use native JS properties for accurate height measurements
363                var containerHeight = containerEl.clientHeight;
364                var scrollTop = containerEl.scrollTop;
365                var scrollHeight = containerEl.scrollHeight;
366
367                // Add a 10px buffer to handle Android 9 sub-pixel fractional rounding bugs
368                if (scrollTop + containerHeight >= scrollHeight - 10) {
369                    // Check scroll bounds and loading lock state
370                    if (!self._isLoading && parseInt(self._currentPage, 10) < parseInt(self._totalPages, 10)) {
371                        self.getLocationsList(null, parseInt(self._currentPage, 10) + 1);
372                    }
373                }
374            });
375        }
376    };
377
378    locationsList.init();
379};
380
381window.checkoutForm = {
382    checkoutCaptcha: null,
383    _deferInitServiceFee: false,
384    _paymentInitAjaxPosted: false,
385
386    abortCheckoutAjax: function (key) {
387        var xhr = window['xhrCheckout' + key];
388
389        if (xhr) {
390            xhr.abort();
391        }
392    },
393
394    setCheckoutAjax: function (key, jqXHR) {
395        window['xhrCheckout' + key] = jqXHR;
396    },
397
398    finishDeferredInitServiceFee: function () {
399        if (!this._deferInitServiceFee) {
400            return;
401        }
402
403        if ($('.js-payment-form-wrapper-pay-now').length) {
404            return;
405        }
406
407        this._deferInitServiceFee = false;
408        this.runInitServiceFeeAutoSelect(true);
409    },
410
411    init: function() {
412        var self = this,
413            $body = $('body'),
414            $checkoutForm = this.getCheckoutFormEl();
415
416        var $wrapper = this.getWrapperEl();
417
418        if ($checkoutForm && $checkoutForm.length) {
419            var $shipDifferent = this.getShipDifferentAddressEl();
420            if ($shipDifferent.length) {
421                $shipDifferent.change(function (e) {
422                    if ($(this).is(':checked')) {
423                        $checkoutForm.find('#shipping-details').show();
424                        $checkoutForm.find('.js-same-as-billing').val(0);
425                    } else {
426                        $checkoutForm.find('#shipping-details').hide();
427                        $checkoutForm.find('.js-same-as-billing').val(1);
428                    }
429
430                }).trigger('change');
431            }
432
433            $body.on('change', '.js-address-update', function () {
434                self.updateOrderDetails();
435            });
436
437            $body.on('change', '.js-input-update-summary', function (e) {
438                self.updateOrderDetails();
439            });
440
441            $body.on('change', '[name="shipping_method"]', function (e) {
442                self.updateOrderShippingMethod();
443            });
444
445            $body.on('change', '[name="payment_method"]', function (e) {
446                self.reEvaluatePaymentMethodBillingAddress();
447                self.updateOrderPaymentMethod();
448            });
449
450            $body.on('change', 'input[name="service_fee_option"]', function (e) {
451                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
452                    return;
453                }
454                self.handleServiceFeeOptionChange(e, self.getServiceFeeSlotConfig(1));
455            });
456
457            $body.on('change keyup', 'input[name="user_service_fee_price"]', function (e) {
458                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
459                    return;
460                }
461                flex.utils.debounce('user_service_fee_price_keyup', function () {
462                    self.handleServiceFeeCustomAmountChange(self.getServiceFeeSlotConfig(1));
463                }, 800);
464            });
465
466            $body.on('change', 'input[name="service_fee_2_option"]', function (e) {
467                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
468                    return;
469                }
470                self.handleServiceFeeOptionChange(e, self.getServiceFeeSlotConfig(2));
471            });
472
473            $body.on('change keyup', 'input[name="user_service_fee_2_price"]', function (e) {
474                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
475                    return;
476                }
477                flex.utils.debounce('user_service_fee_price_2_keyup', function () {
478                    self.handleServiceFeeCustomAmountChange(self.getServiceFeeSlotConfig(2));
479                }, 800);
480            });
481
482            $body.on('change', 'input[name="service_fee_3_option"]', function (e) {
483                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
484                    return;
485                }
486                self.handleServiceFeeOptionChange(e, self.getServiceFeeSlotConfig(3));
487            });
488
489            $body.on('change keyup', 'input[name="user_service_fee_3_price"]', function (e) {
490                if ($(e.currentTarget).closest('.js-payment-form-wrapper-pay-now').length) {
491                    return;
492                }
493                flex.utils.debounce('user_service_fee_price_3_keyup', function () {
494                    self.handleServiceFeeCustomAmountChange(self.getServiceFeeSlotConfig(3));
495                }, 800);
496            });
497
498            $body.on('click', '.js-switch-delivery-address-option', function(e) {
499                e.preventDefault();
500                self.switchDeliveryAddressOption();
501            });
502
503            $body.on('click', '.js-apply-loyalty', function (e) {
504                e.preventDefault();
505
506                $button = $('.js-loyalty-container').find('.js-apply-loyalty');
507
508                self.updateDiscountWithLoyaltyPoints($button.attr('data-action'));
509            });
510
511            self.createAnAccountListener();
512            self.venueAndRoomListener();
513            self.adjustShippingDisplayForVenue();
514            self.reEvaluatePaymentMethodBillingAddress();
515        }
516
517        // show loader on submit
518        if ($wrapper.length) {
519            self._deferInitServiceFee = true;
520            self._paymentInitAjaxPosted = false;
521
522            var $paymentMethods = self.getPaymentMethodsEl();
523
524            // select first payment
525            var checked = $paymentMethods.filter(':checked');
526            if (checked.length) {
527                checked.trigger('change');
528            } else {
529                var $paymentMethodCreditCard = $paymentMethods
530                    .filter('[data-isCreditCard="1"]').first();
531
532                if ($paymentMethodCreditCard.length) {
533                    $paymentMethodCreditCard
534                        .prop('checked', 'checked')
535                        .trigger('change');
536                } else {
537                    $paymentMethods
538                        .first()
539                        .prop('checked', 'checked')
540                        .trigger('change');
541                }
542            }
543
544            $wrapper.find('.js-edit-invoice-po').click(function (e) {
545                e.preventDefault();
546                $wrapper.find('.js-submit-payment').prop('disabled', true);
547                $wrapper.find('.js-edit-invoice-po').addClass('hidden');
548                $wrapper.find('.js-save-invoice-po').html('Save');
549                $wrapper.find('.js-save-invoice-po').removeClass('hidden');
550                $wrapper.find('.js-purchase-order-number').prop('disabled', false);
551            });
552
553            $wrapper.find('.js-save-invoice-po').click(function (e) {
554                e.preventDefault();
555
556                $wrapper.find('.js-purchase-order-number').prop('disabled', true);
557                let order_uuid = $wrapper.find('.js-order-uuid').val();
558                $.ajax({
559                    type: 'post',
560                    data: {
561                        client_po: $wrapper.find('.js-purchase-order-number').val()
562                    },
563                    url: baseUrl + 'order/update/' + order_uuid,
564                    dataType: 'json',
565                    beforeSend:function() {
566                        $wrapper.find('.js-save-invoice-po').html(i18n.gettext('<img src="/themes/frontend/base/assets/images/preloader.gif" class="img-loader"/>'));
567                    },
568                    success: function (data) {
569                        $wrapper.find('.js-save-invoice-po').addClass('hidden');
570                        $wrapper.find('.js-edit-invoice-po').removeClass('hidden');
571                        $wrapper.find('.js-submit-payment').prop('disabled', false);
572                    }
573                });
574            });
575
576            $wrapper.find('.js-submit-payment').click(function (e) {
577                var $formInWrapper = $(this).closest('.js-payment-form-wrapper').find('form.js-payment-form');
578                if ($formInWrapper.parsley().validate()) {
579                    $('.loading-order').show();
580                    $formInWrapper.submit();
581                }
582            });
583        }
584
585        this.tipListener();
586        this.tip2Listener();
587        this.tip3Listener();
588
589        if (self._deferInitServiceFee && !self._paymentInitAjaxPosted) {
590            self.finishDeferredInitServiceFee();
591        }
592
593        this.emailValidationListener();
594
595        // make now active
596        setTimeout(function() {
597            $checkoutForm.removeClass('disabled');
598        }, 600);
599    },
600
601    submitLoading: function(loading) {
602        loading = typeof loading !== 'undefined' ? loading : false;
603        var $wrapper = this.getWrapperEl();
604        var $submit = $wrapper.find('.js-submit-payment');
605
606        if (loading) {
607            $submit.addClass('disabled');
608        } else {
609            $submit.removeClass('disabled');
610        }
611    },
612
613    triggerCodeChallenge: function() {
614        var $wrapper = this.getWrapperEl();
615        var $modal = $('#modal-email-challenge');
616        var $form = this.getCheckoutFormEl();
617        var isGuest = $form.data('is-guest');
618        var requireVerification = $form.data('email-require-verification');
619        var email = $form.data('email');
620        var $modalBody = $modal.find('.modal-body');
621        var $btn = $modal.find('.js-verify-continue');
622        var $input = $modal.find('.js-input-code');
623        var $resend = $modal.find('.js-resend');
624        var countdownInterval = null;
625
626        function submit(code) {
627            $btn.addClass('disabled');
628            $.ajax({
629                type: 'post',
630                data: {
631                    is_guest: isGuest ? 1 : 0,
632                    email: email,
633                    code: code,
634                },
635                url: baseUrl + 'order/checkout/validate_challenge',
636                dataType: 'json',
637                success: function (data) {
638                    $btn.removeClass('disabled');
639
640                    if (data.status === 'error') {
641                        alert(data.message);
642                        return ;
643                    }
644
645                    $form.find('input[name="_email_challenge_verified"]').val(1);
646                    $form.find('input[name="_email_challenge_email"]').val(email);
647                    $modal.modal('hide');
648                    setTimeout(function() {
649                        $wrapper.find('.js-submit-payment').trigger('click');
650                    }, 1000);
651                }
652            })
653        }
654
655        function sendCode(isResend) {
656            isResend = typeof isResend !== 'undefined' ? isResend : false;
657            $modalBody.addClass('disabled');
658            console.log('js-email', $modalBody.find('.js-email'))
659            $modalBody.find('.js-email').text(email);
660
661            $.ajax({
662                type: 'post',
663                data: {
664                    is_guest: isGuest ? 1 : 0,
665                    email: email,
666                },
667                url: baseUrl + 'order/checkout/send_challenge',
668                dataType: 'json',
669                success: function (data) {
670                    $modalBody.removeClass('disabled');
671
672                    $btn.addClass('disabled');
673                    $input.off('keyup change').on('keyup change', function() {
674                        if ($(this).val()) {
675                            $btn.removeClass('disabled');
676                        } else {
677                            $btn.addClass('disabled');
678                        }
679                    });
680
681                    $btn.off('click').on('click', function(e) {
682                        e.preventDefault();
683
684                        submit($input.val());
685                    });
686
687                    if (isResend) {
688                        var $newCodeSent = $modalBody.find('.js-new-code-sent');
689                        var $newCodeSentStatus = $modalBody.find('.js-email-challenge-announce');
690                        var message = $newCodeSent.text().trim();
691
692                        // update the new code sent status
693                        $newCodeSentStatus.text('');
694                        $newCodeSentStatus.text(message);
695
696                        $newCodeSent.slideDown();
697
698                        // retain focus on the input
699                        setTimeout(function() {
700                            $input.focus();
701                        }, 100);
702
703                        setTimeout(function() {
704                            $newCodeSent.slideUp(function () {
705                                $newCodeSent.attr('aria-hidden', 'true');
706                            });
707                        }, 3000);
708
709                        var countDown = 30;
710                        clearInterval(countdownInterval);
711                        $resend.addClass('disabled');
712
713                        countdownInterval = setInterval(function() {
714                            countDown -= 1;
715
716                            if (countDown <= 0) {
717                                $modal.find('.js-resend-countdown-wrapper').hide();
718                                $resend.removeClass('disabled');
719                            } else {
720                                $modal.find('.js-resend-countdown-wrapper').show();
721                                $modal.find('.js-resend-countdown').text(countDown);
722                                $resend.addClass('disabled');
723                            }
724                        }, 1000);
725                    } else {
726                        $modal.find('.js-resend-countdown-wrapper').hide();
727                        $resend.removeClass('disabled');
728                    }
729                }
730            });
731        }
732
733        $resend.off('click').on('click', function(e) {
734            e.preventDefault();
735            sendCode(true);
736        });
737
738        $modal.off('shown.bs.modal.email-challenge-announce').on('shown.bs.modal.email-challenge-announce', function () {
739            var description = $modal.find('#email-challenge-modal-description').text().trim();
740            $modal.find('.js-email-challenge-announce').text(description);
741        });
742
743        // if guest
744        // then past the email and validate\
745        if (requireVerification) {
746            if (isGuest) {
747                if ($form.data('guest-email-is-valid')) {
748                    sendCode();
749                    $modal.appendTo('body');
750                    $modal.modal('show');
751                }
752            } else {
753                sendCode();
754                $modal.appendTo('body');
755                $modal.modal('show');
756            }
757        }
758    },
759
760    /**
761     * called via themes/frontend/base/js/validate-captcha.js
762     */
763    checkoutCaptcha: function() {
764
765
766        /* Hidden recaptch is disabled for now*/
767        var $form = this.getCheckoutFormEl();
768
769        if (this.getCheckoutFormEl().length && $('#checkout_recaptcha').length) {
770            this.checkoutCaptcha = grecaptcha.render('checkout_recaptcha', {
771                'sitekey': $form.find('#checkout_recaptcha').attr('data-sitekey'),
772            });
773        }
774    },
775
776    venueAndRoomListener: function() {
777        var self = this,
778            $checkoutForm = self.getCheckoutFormEl(),
779            $venueEl = $checkoutForm.find('.js-select-venue'),
780            $venueRoomEl = $checkoutForm.find('.js-select-main-room'),
781            $venueBusinessNameEl = $checkoutForm.find('.js-venue-business-name'),
782            $venueAddress = $checkoutForm.find('.js-venue-address'),
783            $venueAddress2 = $checkoutForm.find('.js-venue-address2'),
784            $venuePostcode = $checkoutForm.find('.js-venue-postcode'),
785            $venueSuburb = $checkoutForm.find('.js-venue-suburb'),
786            $venueState = $checkoutForm.find('.js-venue-state'),
787            $venueCountry = $checkoutForm.find('.js-venue-country'),
788            $venuePhone = $checkoutForm.find('.js-venue-phone'),
789            $venueAddressExceptStEl = $checkoutForm.find('.js-venue-address-except-st');
790
791        var venueSelect = new flex.venue.selectVenue({
792            $select: $venueEl,
793            $selectRoom: $venueRoomEl,
794            onChangeRoom: function(data) {
795                // update venue fields
796                $venueBusinessNameEl.val(data.venue && data.venue.company ? data.venue.company : ''); // hidden
797                $venueAddress.val(data.venue && data.venue.address ? data.venue.address : '');
798                $venueAddress2.val(data.venue && data.venue.address2 ? data.venue.address2 : '');
799                $venuePostcode.val(data.venue && data.venue.postcode ? data.venue.postcode : ''); // hidden
800                $venueSuburb.val(data.venue && data.venue.suburb ? data.venue.suburb : ''); // hidden
801                $venueState.val(data.venue && data.venue.state ? data.venue.state : ''); // hidden
802                $venueCountry.val(data.venue && data.venue.country ? data.venue.country : ''); // hidden
803                $venuePhone.val(data.venue && data.venue.phone ? data.venue.phone : '');
804
805                $venueAddressExceptStEl.val([
806                    $venueSuburb.val(),
807                    $venueState.val(),
808                    $venuePostcode.val(),
809                    $venueCountry.val(),
810                ].join(' ').trim());
811
812                self.adjustShippingDisplayForVenue();
813
814                self.updateOrderDetails();
815            }
816        });
817
818        venueSelect.updateRooms();
819        venueSelect.updateRoomValue($venueRoomEl.val());
820        venueSelect.options.$selectRoom.change(function(e){
821            if (venueSelect.options.$select.val()) {
822                self.updateOrderDetails();
823            }
824        });
825    },
826
827    adjustShippingDisplayForVenue: function() {
828        var self = this,
829            $checkoutForm = self.getCheckoutFormEl(),
830            $deliveryAddressOptionEl = $checkoutForm.find('.js-delivery-address-option'),
831            $venueEl = $checkoutForm.find('.js-select-venue'),
832            $venueAddress = $checkoutForm.find('.js-venue-address'),
833            $venueAddress2 = $checkoutForm.find('.js-venue-address2'),
834            $venuePhone = $checkoutForm.find('.js-venue-phone'),
835            $venueAddressExceptStEl = $checkoutForm.find('.js-venue-address-except-st'),
836            hideElementClosest = function($el){
837                $el.closest('div.form-group').hide();
838            },
839            showElementClosest = function($el){
840                if ($el.val()) {
841                    $el.closest('div.form-group').show();
842                }
843            };
844
845        if ($deliveryAddressOptionEl.val() != 'venue') return;
846
847        hideElementClosest($venueAddress);
848        hideElementClosest($venueAddress2);
849        hideElementClosest($venuePhone);
850        hideElementClosest($venueAddressExceptStEl);
851
852        if ($venueEl.val()) {
853            showElementClosest($venueAddress);
854            showElementClosest($venueAddress2);
855            showElementClosest($venuePhone);
856            showElementClosest($venueAddressExceptStEl);
857        }
858    },
859
860    switchDeliveryAddressOption: function() {
861        var self = this,
862            $checkoutForm = this.getCheckoutFormEl(),
863            $switchDeliveryAddresEl = $checkoutForm.find('.js-switch-delivery-address-option'),
864            $deliveryAddressOptionEl = $checkoutForm.find('.js-delivery-address-option'),
865            $shipDifferentAddressEl = $checkoutForm.find('.js-ship-different-address'),
866
867            currentDeliveryAddressOption = $checkoutForm.find('.js-delivery-address-option:checked').val(),
868            currentDeliveryAddressOptionText = $switchDeliveryAddresEl.text(),
869            refreshShippingCheckoutAddressBox = false;
870
871        // switch
872        if (currentDeliveryAddressOption == 'manual') {
873            currentDeliveryAddressOption = 'venue';
874            currentDeliveryAddressOptionText = i18n.gettext('Manual Address');
875            $shipDifferentAddressEl.prop('checked', true).trigger('change');
876        } else {
877            currentDeliveryAddressOption = 'manual';
878            currentDeliveryAddressOptionText = i18n.gettext('Venue Address');
879            $shipDifferentAddressEl.prop('checked', false).trigger('change');
880            refreshShippingCheckoutAddressBox = true;
881            $('.js-address-box-type--shipping').addClass('loading-with-skeleton');
882        }
883
884        $switchDeliveryAddresEl.text(currentDeliveryAddressOptionText);
885        $deliveryAddressOptionEl.prop('checked', false);
886        $checkoutForm.find('.js-delivery-address-option[value="' + currentDeliveryAddressOption + '"]').prop('checked', true).trigger('change');
887
888        self.adjustShippingDisplayForVenue();
889
890        self.updateOrderDetails(refreshShippingCheckoutAddressBox);
891    },
892
893    syncServiceFeeOptionUi: function ($inputs, $custom, $selected) {
894        $inputs.not($selected).prop('checked', false);
895        $custom.hide();
896        if ($selected.val() == flexconfig.order.SERVICE_FEE_TYPE_FIXED && $selected.is(':checked')) {
897            $custom.show();
898        }
899    },
900
901    getServiceFeeSlotConfig: function (slot) {
902        var configs = {
903            1: {
904                wrapperSelector: '.js-service-fee-wrapper',
905                optionsSelector: '.service-fee-options',
906                customSelector: '.js-custom-amount',
907                optionName: 'service_fee_option',
908                updateMethod: 'updateServiceFeeAmount'
909            },
910            2: {
911                wrapperSelector: '.js-service-fee-2-wrapper',
912                optionsSelector: '.service-fee-2-options',
913                customSelector: '.js-custom-amount-2',
914                optionName: 'service_fee_2_option',
915                updateMethod: 'updateServiceFee2Amount'
916            },
917            3: {
918                wrapperSelector: '.js-service-fee-3-wrapper',
919                optionsSelector: '.service-fee-3-options',
920                customSelector: '.js-custom-amount-3',
921                optionName: 'service_fee_3_option',
922                updateMethod: 'updateServiceFee3Amount'
923            }
924        };
925
926        return configs[slot];
927    },
928
929    resolveServiceFeeOptionPostValue: function ($wrapper, optionName, $selectedInput) {
930        if ($selectedInput && $selectedInput.length) {
931            return $selectedInput.is(':checked') ? $selectedInput.val() : '';
932        }
933
934        var $checked = $wrapper.find('input[name="' + optionName + '"]:checked');
935
936        return $checked.length ? $checked.val() : '';
937    },
938
939    handleServiceFeeOptionChange: function (e, slotConfig) {
940        var self = this,
941            $clicked = $(e.currentTarget),
942            $wrapper = $clicked.closest(slotConfig.wrapperSelector),
943            $optionsContainer = $wrapper.find(slotConfig.optionsSelector),
944            $inputs = $wrapper.find('input[name=' + slotConfig.optionName + ']'),
945            $custom = $wrapper.find(slotConfig.customSelector),
946            isMandatory = $optionsContainer.length
947                && parseInt($optionsContainer.data('mandatory'), 10) === 1;
948
949        if (!$wrapper.length) {
950            return;
951        }
952
953        if (isMandatory && !$clicked.is(':checked')) {
954            $clicked.prop('checked', true);
955
956            return;
957        }
958
959        var isEnterAmount = $clicked.val() == flexconfig.order.SERVICE_FEE_TYPE_FIXED,
960            isEnterAmountSelect = isEnterAmount && $clicked.is(':checked');
961
962        if ($clicked.is(':checked')) {
963            self.syncServiceFeeOptionUi($inputs, $custom, $clicked);
964        } else {
965            $custom.hide();
966        }
967
968        if (!isEnterAmountSelect) {
969            self[slotConfig.updateMethod](false, $clicked);
970        }
971    },
972
973    getCheckedEnterAmountOptionInput: function ($feeWrapper, optionName) {
974        var $fixedInput = $feeWrapper.find(
975            'input[name="' + optionName + '"][value="' + flexconfig.order.SERVICE_FEE_TYPE_FIXED + '"]'
976        );
977
978        return $fixedInput.is(':checked') ? $fixedInput : null;
979    },
980
981    handleServiceFeeCustomAmountChange: function (slotConfig) {
982        var self = this,
983            $feeWrapper = self.getWrapperEl().find(slotConfig.wrapperSelector),
984            $fixedInput = self.getCheckedEnterAmountOptionInput($feeWrapper, slotConfig.optionName);
985
986        if (!$fixedInput) {
987            return;
988        }
989
990        self[slotConfig.updateMethod](false, $fixedInput);
991    },
992
993    syncServiceFeeCustomAmountVisibility: function (slotConfig) {
994        var $feeWrapper = this.getWrapperEl().find(slotConfig.wrapperSelector),
995            $fixedInput = this.getCheckedEnterAmountOptionInput($feeWrapper, slotConfig.optionName);
996
997        if (!$feeWrapper.length || !$fixedInput) {
998            return;
999        }
1000
1001        this.syncServiceFeeOptionUi(
1002            $feeWrapper.find('input[name=' + slotConfig.optionName + ']'),
1003            $feeWrapper.find(slotConfig.customSelector),
1004            $fixedInput
1005        );
1006    },
1007
1008    syncAllServiceFeeCustomAmountVisibility: function () {
1009        var self = this;
1010
1011        [1, 2, 3].forEach(function (slot) {
1012            self.syncServiceFeeCustomAmountVisibility(self.getServiceFeeSlotConfig(slot));
1013        });
1014    },
1015
1016    snapshotEnterAmountCheckedBySlot: function () {
1017        var self = this,
1018            snapshot = {};
1019
1020        [1, 2, 3].forEach(function (slot) {
1021            var slotConfig = self.getServiceFeeSlotConfig(slot),
1022                $feeWrapper = self.getWrapperEl().find(slotConfig.wrapperSelector);
1023
1024            snapshot[slot] = !!self.getCheckedEnterAmountOptionInput($feeWrapper, slotConfig.optionName);
1025        });
1026
1027        return snapshot;
1028    },
1029
1030    restoreEnterAmountSelectionAfterControlsRefresh: function (enterAmountWasCheckedBySlot) {
1031        var self = this;
1032
1033        [1, 2, 3].forEach(function (slot) {
1034            if (!enterAmountWasCheckedBySlot[slot]) {
1035                return;
1036            }
1037
1038            var slotConfig = self.getServiceFeeSlotConfig(slot),
1039                $feeWrapper = self.getWrapperEl().find(slotConfig.wrapperSelector),
1040                $fixedInput = $feeWrapper.find(
1041                    'input[name="' + slotConfig.optionName + '"][value="' + flexconfig.order.SERVICE_FEE_TYPE_FIXED + '"]'
1042                ),
1043                $inputs = $feeWrapper.find('input[name=' + slotConfig.optionName + ']'),
1044                $custom = $feeWrapper.find(slotConfig.customSelector);
1045
1046            if (!$fixedInput.length) {
1047                return;
1048            }
1049
1050            $fixedInput.prop('checked', true);
1051            self.syncServiceFeeOptionUi($inputs, $custom, $fixedInput);
1052        });
1053    },
1054
1055    postServiceFeeForOption: function (optionName, silent) {
1056        if (optionName === 'service_fee_option') {
1057            this.updateServiceFeeAmount(silent);
1058        } else if (optionName === 'service_fee_2_option') {
1059            this.updateServiceFee2Amount(silent);
1060        } else if (optionName === 'service_fee_3_option') {
1061            this.updateServiceFee3Amount(silent);
1062        }
1063    },
1064
1065    applyInitServiceFeeSlot: function (optionName, customSelector, presetSelector, optionsSelector, silent) {
1066        var self = this,
1067            $wrapper = this.getWrapperEl(),
1068            $custom = $wrapper.find(customSelector),
1069            $inputs = $wrapper.find('input[name=' + optionName + ']'),
1070            $serviceFeeOptionsInputs = $wrapper.find(presetSelector),
1071            $optionsContainer = $wrapper.find(optionsSelector),
1072            $checked = $inputs.filter(':checked');
1073
1074        if ($optionsContainer.length && parseInt($optionsContainer.data('cart-qualifies'), 10) !== 1) {
1075            return;
1076        }
1077
1078        if ($checked.length) {
1079            if (!silent) {
1080                $checked.trigger('change');
1081            } else if ($checked.val() == flexconfig.order.SERVICE_FEE_TYPE_FIXED) {
1082                self.syncServiceFeeOptionUi($inputs, $custom, $checked);
1083            } else {
1084                $custom.hide();
1085            }
1086
1087            return;
1088        }
1089
1090        if ($checked.length === 0
1091            && $optionsContainer.length
1092            && parseInt($optionsContainer.data('mandatory'), 10) === 1
1093            && $serviceFeeOptionsInputs.length > 0) {
1094            var $firstMandatoryPreset = $serviceFeeOptionsInputs.first();
1095            if (silent) {
1096                $firstMandatoryPreset.prop('checked', true);
1097                self.syncServiceFeeOptionUi($inputs, $custom, $firstMandatoryPreset);
1098                self.postServiceFeeForOption(optionName, true);
1099            } else {
1100                $firstMandatoryPreset.prop('checked', true).trigger('change');
1101            }
1102        }
1103
1104        if (!silent && $custom.length && $inputs.filter(':checked').length) {
1105            var $selected = $inputs.filter(':checked');
1106
1107            if ($selected.val() == flexconfig.order.SERVICE_FEE_TYPE_FIXED) {
1108                $custom.show();
1109            }
1110        }
1111    },
1112
1113    runInitServiceFeeAutoSelect: function (silent) {
1114        this.applyInitServiceFeeSlot(
1115            'service_fee_option',
1116            '.js-custom-amount',
1117            '.js-service-fee-options input[name=service_fee_option]',
1118            '.service-fee-options',
1119            silent
1120        );
1121        this.applyInitServiceFeeSlot(
1122            'service_fee_2_option',
1123            '.js-custom-amount-2',
1124            '.js-service-fee-2-options input[name=service_fee_2_option]',
1125            '.service-fee-2-options',
1126            silent
1127        );
1128        this.applyInitServiceFeeSlot(
1129            'service_fee_3_option',
1130            '.js-custom-amount-3',
1131            '.js-service-fee-3-options input[name=service_fee_3_option]',
1132            '.service-fee-3-options',
1133            silent
1134        );
1135    },
1136
1137    tipListener: function() {
1138        var self = this;
1139
1140        if (!self._deferInitServiceFee) {
1141            self.applyInitServiceFeeSlot(
1142                'service_fee_option',
1143                '.js-custom-amount',
1144                '.js-service-fee-options input[name=service_fee_option]',
1145                '.service-fee-options'
1146            );
1147        }
1148    },
1149
1150    tip2Listener: function() {
1151        var self = this;
1152
1153        if (!self._deferInitServiceFee) {
1154            self.applyInitServiceFeeSlot(
1155                'service_fee_2_option',
1156                '.js-custom-amount-2',
1157                '.js-service-fee-2-options input[name=service_fee_2_option]',
1158                '.service-fee-2-options'
1159            );
1160        }
1161    },
1162
1163    tip3Listener: function() {
1164        var self = this;
1165
1166        if (!self._deferInitServiceFee) {
1167            self.applyInitServiceFeeSlot(
1168                'service_fee_3_option',
1169                '.js-custom-amount-3',
1170                '.js-service-fee-3-options input[name=service_fee_3_option]',
1171                '.service-fee-3-options'
1172            );
1173        }
1174    },
1175
1176    emailValidationListener: function() {
1177        var xhr, self = this;
1178        var $checkoutForm = this.getCheckoutFormEl();
1179        $checkoutForm.on('keyup', '#form_billing_email', _.debounce(function (e) {
1180            !xhr || xhr.abort();
1181
1182            self.submitLoading(true);
1183            var emailVal = $(this).val();
1184            xhr = $.ajax({
1185                type: 'get',
1186                data: {
1187                    email: emailVal,
1188                },
1189                dataType: 'json',
1190                url: baseUrl + 'order/cart/validate_email',
1191                success: function (data) {
1192                    var $emailExisist = $checkoutForm.find('#email-exists');
1193                    $checkoutForm.data('guest-email-is-valid', 0);
1194
1195                    if (data.status === 'success') {
1196                        $emailExisist.hide();
1197
1198                        // status is returned success so the email exists message is not showndata-guest-email-is-valid
1199                        if (data.email_is_valid) {
1200                            $checkoutForm.data('guest-email-is-valid', 1);
1201                            $checkoutForm.data('email', emailVal);
1202                        }
1203                    } else {
1204                        $emailExisist.show();
1205                    }
1206                }
1207            }).always(function() {
1208                self.submitLoading(false);
1209            });
1210        }, 800));
1211
1212        $checkoutForm.find("#form_billing_email").trigger('keyup');
1213    },
1214
1215    updateOrderDetails: _.debounce(function(refreshShippingCheckoutAddressBox = false) {
1216        var self = this,
1217            $checkoutForm = this.getCheckoutFormEl();
1218
1219        self.abortCheckoutAjax('OrderDetails');
1220
1221        if ($checkoutForm.length) {
1222            self.setCheckoutAjax('OrderDetails', $.ajax({
1223                type: 'post',
1224                data: $checkoutForm.serialize(),
1225                url: baseUrl + 'order/checkout/update_order_details',
1226                dataType: 'json',
1227                success: function (data) {
1228                    flex.cart.updateCartInformation(data.cart_information);
1229
1230                    if (refreshShippingCheckoutAddressBox) {
1231                        flex.account.refreshCheckoutAddressBox('shipping');
1232                    }
1233
1234                    if (data.reload_to_update_payment) {
1235                        alert(data.reload_to_update_payment_message);
1236                        window.location.reload();
1237                    }
1238                }
1239            }));
1240        }
1241    }, 600),
1242
1243    updateShippingMethods: function() {
1244        var $shippingMethodsContainer = $('.js-shipping-methods-container');
1245        if (typeof cartInformation.shipping_methods_html !== 'undefined'
1246            && $shippingMethodsContainer.length) {
1247            $shippingMethodsContainer.html(cartInformation.shipping_methods_html);
1248        }
1249    },
1250
1251    updateOrderShippingMethod: function () {
1252        var self = this,
1253            getCheckoutFormEl = this.getCheckoutFormEl();
1254
1255
1256        self.abortCheckoutAjax('ShippingMethod');
1257
1258        if (getCheckoutFormEl.length) {
1259            self.hidePlaceOrderSection();
1260            self.setCheckoutAjax('ShippingMethod', $.ajax({
1261                type: 'post',
1262                data: {shipping_method: getCheckoutFormEl.find('[name="shipping_method"]:checked').val()},
1263                url: baseUrl + 'order/cart/update_order_shipping_method',
1264                dataType: 'json',
1265                success: function (data) {
1266                    flex.cart.updateCartInformation(data.cart_information);
1267                }
1268            }));
1269        }
1270    },
1271
1272    updateOrderPaymentMethod: function() {
1273        var self = this,
1274            $wrapper = self.getWrapperEl();
1275
1276        self.abortCheckoutAjax('PaymentMethod');
1277        self.hidePlaceOrderSection();
1278        self._paymentInitAjaxPosted = true;
1279
1280        self.setCheckoutAjax('PaymentMethod', $.ajax({
1281            type: 'post',
1282            data: {payment_method: $wrapper.find('[name="payment_method"]:checked').val()},
1283            url: baseUrl + 'order/cart/update_order_payment_method',
1284            dataType: 'json',
1285            success: function (data) {
1286                flex.cart.updateCartInformation(data.cart_information);
1287            },
1288            complete: function () {
1289                self.finishDeferredInitServiceFee();
1290            }
1291        }));
1292    },
1293
1294    /**
1295     * Re-evaluate if we should show payment method billing address container
1296     *
1297     * @return {void}
1298     */
1299    reEvaluatePaymentMethodBillingAddress: function() {
1300        var self = this,
1301            $wrapper = self.getWrapperEl(),
1302            $pmAddress = self.getPaymentMethodBillingAddressContainer(),
1303            $checkedPaymentMethod = $wrapper.find('[name="payment_method"]').filter(':checked'),
1304            $checkedCard = $wrapper.find('.js-change-show-payment_method-' + $checkedPaymentMethod.val()).find('.js-credit-card-save-select:checked'),
1305            shouldShow = $checkedPaymentMethod.data('show-payment-method-billing-address');
1306
1307        $pmAddress.hide();
1308        if (parseInt(shouldShow) === 1 && ! $checkedCard.val()) {
1309            $pmAddress.show();
1310        }
1311    },
1312
1313    updateServiceFeeAmount: function(silent, $selectedInput) {
1314        var self = this,
1315            $wrapper = self.getWrapperEl(),
1316            $userServiceFeePrice = $wrapper.find('.js-user-service-fee-price'),
1317            $userServiceFeePriceFormatted = $wrapper.find('.js-user-service-fee-price-formatted');
1318        self.abortCheckoutAjax('ServiceFee1');
1319
1320        if (!silent) {
1321            $wrapper.addClass('disabled');
1322            !window.xhrUsfa || clearTimeout(window.xhrUsfa);
1323            window.xhrUsfa = setTimeout(function() {
1324                $wrapper.removeClass('disabled');
1325            }, 700);
1326
1327            self.hidePlaceOrderSection();
1328        }
1329        self.setCheckoutAjax('ServiceFee1', $.ajax({
1330            type: 'post',
1331            data: {
1332                service_fee_option: self.resolveServiceFeeOptionPostValue(
1333                    $wrapper,
1334                    'service_fee_option',
1335                    $selectedInput
1336                ),
1337                user_service_fee_price: $wrapper.find('input[name="user_service_fee_price"]').val()
1338            },
1339            url: baseUrl + 'order/cart/update_service_fee_options',
1340            dataType: 'json',
1341            success: function (data) {
1342                // update user tip price
1343                if (data.status ==  'success') {
1344                    if (!$userServiceFeePrice.is(':focus')) {
1345                        $userServiceFeePrice.val(data.cart_information.user_service_fee_price);
1346                    }
1347
1348                    $userServiceFeePriceFormatted.text(flex.formatMoney(data.cart_information.user_service_fee_price));
1349                    flex.cart.updateCartInformation(data.cart_information);
1350                }
1351            }
1352        }));
1353    },
1354
1355    updateServiceFee2Amount: function(silent, $selectedInput) {
1356        var self = this,
1357            $wrapper = self.getWrapperEl(),
1358            $userServiceFee2Price = $wrapper.find('.js-user-service-fee-2-price'),
1359            $userServiceFee2PriceFormatted = $wrapper.find('.js-user-service-fee-2-price-formatted');
1360
1361        self.abortCheckoutAjax('ServiceFee2');
1362
1363        if (!silent) {
1364            $wrapper.addClass('disabled');
1365            !window.xhrUsfa2 || clearTimeout(window.xhrUsfa2);
1366            window.xhrUsfa2 = setTimeout(function() {
1367                $wrapper.removeClass('disabled');
1368            }, 700);
1369
1370            self.hidePlaceOrderSection();
1371        }
1372        self.setCheckoutAjax('ServiceFee2', $.ajax({
1373            type: 'post',
1374            data: {
1375                service_fee_2_option: self.resolveServiceFeeOptionPostValue(
1376                    $wrapper,
1377                    'service_fee_2_option',
1378                    $selectedInput
1379                ),
1380                user_service_fee_2_price: $wrapper.find('input[name="user_service_fee_2_price"]').val()
1381            },
1382            url: baseUrl + 'order/cart/update_service_fee_2_options',
1383            dataType: 'json',
1384            success: function (data) {
1385                // update user tip price
1386                if (data.status ==  'success') {
1387                    if (!$userServiceFee2Price.is(':focus')) {
1388                        $userServiceFee2Price.val(data.cart_information.user_service_fee_2_price);
1389                    }
1390
1391                    $userServiceFee2PriceFormatted.text(flex.formatMoney(data.cart_information.user_service_fee_2_price));
1392                    flex.cart.updateCartInformation(data.cart_information);
1393                }
1394            }
1395        }));
1396    },
1397
1398    updateServiceFee3Amount: function(silent, $selectedInput) {
1399        var self = this,
1400            $wrapper = self.getWrapperEl(),
1401            $userServiceFee3Price = $wrapper.find('.js-user-service-fee-3-price'),
1402            $userServiceFee3PriceFormatted = $wrapper.find('.js-user-service-fee-3-price-formatted');
1403
1404        self.abortCheckoutAjax('ServiceFee3');
1405
1406        if (!silent) {
1407            $wrapper.addClass('disabled');
1408            !window.xhrUsfa3 || clearTimeout(window.xhrUsfa3);
1409            window.xhrUsfa3 = setTimeout(function() {
1410                $wrapper.removeClass('disabled');
1411            }, 700);
1412
1413            self.hidePlaceOrderSection();
1414        }
1415        self.setCheckoutAjax('ServiceFee3', $.ajax({
1416            type: 'post',
1417            data: {
1418                service_fee_3_option: self.resolveServiceFeeOptionPostValue(
1419                    $wrapper,
1420                    'service_fee_3_option',
1421                    $selectedInput
1422                ),
1423                user_service_fee_3_price: $wrapper.find('input[name="user_service_fee_3_price"]').val()
1424            },
1425            url: baseUrl + 'order/cart/update_service_fee_3_options',
1426            dataType: 'json',
1427            success: function (data) {
1428                // update user tip price
1429                if (data.status ==  'success') {
1430                    if (!$userServiceFee3Price.is(':focus')) {
1431                        $userServiceFee3Price.val(data.cart_information.user_service_fee_3_price);
1432                    }
1433
1434                    $userServiceFee3PriceFormatted.text(flex.formatMoney(data.cart_information.user_service_fee_3_price));
1435                    flex.cart.updateCartInformation(data.cart_information);
1436                }
1437            }
1438        }));
1439    },
1440
1441    createAnAccountListener: function() {
1442        var $form = this.getCheckoutFormEl(),
1443            $createAnAccount = $form.find('.js-create-an-account'),
1444            $btnCheckoutAsGuest = this.getWrapperEl().find('.js-place-order-text-as-guest'),
1445            $btnCheckout = this.getWrapperEl().find('.js-place-order-text');
1446
1447        // revalidate checkout state
1448        $createAnAccount.change(function() {
1449            $btnCheckoutAsGuest.hide();
1450            $btnCheckout.hide();
1451
1452            if ($(this).is(':checked')) {
1453                $btnCheckout.show();
1454            } else {
1455                $btnCheckoutAsGuest.show();
1456            }
1457        });
1458
1459        if (typeof window.localStorage !== 'undefined') {
1460            var localKeyStore = '_checkout_create_an_account_input_value';
1461            $createAnAccount.change(function() {
1462                window.localStorage.setItem(localKeyStore, $(this).is(':checked') ? 1 : 0);
1463            });
1464
1465            if (window.localStorage.getItem(localKeyStore) !== null) {
1466                // Commented this, Ren don't want the "Create an account" tickbox to be ticked by default
1467                // $createAnAccount.prop('checked', (window.localStorage.getItem(localKeyStore) == 1));
1468            }
1469        }
1470
1471        $createAnAccount.trigger('change');
1472    },
1473
1474    updateCheckoutInformation: function(cartInformation) {
1475        var
1475 self = this;
1476        var $checkoutForm = this.getCheckoutFormEl(),
1477            $paymentFormWrapper = this.getWrapperEl();
1478
1479        var $cartTotals = $('.js-cart-totals');
1480
1481        var x, total, $total, $prevTotal = false,
1482            cloneTrWithCode = function() {
1483                return $cartTotals.find('>tr[data-total-code]').first().clone()
1484            };
1485
1486        // hide all totals
1487        $cartTotals.find('>tr[data-total-code]').hide();
1488        for (x in cartInformation.totals) {
1489            total = cartInformation.totals[x];
1490
1491            $total = $cartTotals.find('[data-total-code=' + total.code + ']');
1492
1493            if (!$total.length) {
1494                $total = cloneTrWithCode();
1495                $total.attr('data-total-code', total.code);
1496                // if no total founds
1497                if (!$prevTotal) {
1498                    $cartTotals.prepend($total);
1499                } else {
1500                    $total.insertAfter($prevTotal);
1501                }
1502            }
1503
1504            // record the previous total
1505            $prevTotal = $total;
1506
1507            if (total.is_show) {
1508                $total.find('.js-total-labels')
1509                    .text(total.label);
1510                $total.find('.js-total-display')
1511                    .text(total.total_formatted);
1512                $total.show();
1513            } else {
1514                $total.hide();
1515            }
1516        }
1517
1518        $('.js-cart-grand-total').text(cartInformation.grand_total_formatted);
1519        $('.js-grand-total-hidden').val(cartInformation.grand_total);
1520        $('.js-total-items-container').replaceWith(cartInformation.total_items_html);
1521
1522        var $serviceFeeControls = $('.js-service-fee-controls'),
1523            enterAmountSnapshot = {};
1524        if (typeof cartInformation.service_fee_controls_html !== 'undefined'
1525            && $serviceFeeControls.length) {
1526            enterAmountSnapshot = self.snapshotEnterAmountCheckedBySlot();
1527            $serviceFeeControls.html(cartInformation.service_fee_controls_html);
1528            // Preset mutual-exclusivity and POST payload use delegated handleServiceFeeOptionChange.
1529            self.runInitServiceFeeAutoSelect(true);
1530            self.syncAllServiceFeeCustomAmountVisibility();
1531            self.restoreEnterAmountSelectionAfterControlsRefresh(enterAmountSnapshot);
1532        }
1533
1534        var $feeFormatted = $paymentFormWrapper.find('.js-user-service-fee-price-formatted');
1535        if ($feeFormatted.length && typeof cartInformation.user_service_fee_price !== 'undefined') {
1536            $feeFormatted.text(flex.formatMoney(cartInformation.user_service_fee_price));
1537        }
1538
1539        var $fee2Formatted = $paymentFormWrapper.find('.js-user-service-fee-2-price-formatted');
1540        if ($fee2Formatted.length && typeof cartInformation.user_service_fee_2_price !== 'undefined') {
1541            $fee2Formatted.text(flex.formatMoney(cartInformation.user_service_fee_2_price));
1542        }
1543
1544        var $fee3Formatted = $paymentFormWrapper.find('.js-user-service-fee-3-price-formatted');
1545        if ($fee3Formatted.length && typeof cartInformation.user_service_fee_3_price !== 'undefined') {
1546            $fee3Formatted.text(flex.formatMoney(cartInformation.user_service_fee_3_price));
1547        }
1548
1549        flex.globalPlugin.run($('.js-total-items-container'));
1550
1551        // should we show credit card surcharge details?
1552        var $creditCardDetails = $('.js-credit-card-surcharge-details');
1553        if (cartInformation.show_credit_card_surcharge_checkout_message == 1) {
1554            $creditCardDetails.show();
1555        } else {
1556            $creditCardDetails.hide();
1557        }
1558
1559        // should we show ach surcharge details?
1560        var $achDetails = $('.js-ach-surcharge-details');
1561        if (cartInformation.show_ach_surcharge_checkout_message == 1) {
1562            $achDetails.show();
1563        } else {
1564            $achDetails.hide();
1565        }
1566
1567        (function() {
1568            var $labelDelivery = $('.js-order-date-label-delivery'),
1569                $labelPickup = $('.js-order-date-label-pickup'),
1570                // for recurring
1571                $labelNextDelivery = $('.js-next-delivery-date-label-delivery'),
1572                $labelNextPickup = $('.js-next-delivery-date-label-pickup');
1573
1574            $labelDelivery.show();
1575            $labelPickup.hide();
1576
1577            $labelNextDelivery.show();
1578            $labelNextPickup.hide();
1579
1580
1581            if (cartInformation.delivery_option === 'pickup') {
1582                $labelDelivery.hide();
1583                $labelPickup.show();
1584
1585                $labelNextDelivery.hide();
1586                $labelNextPickup.show();
1587            }
1588        })();
1589
1590        // @todo, check this on checkout page
1591        if ($checkoutForm && $checkoutForm.length) {
1592            self.showPlaceOrderSection();
1593            $paymentFormWrapper.find('.js-place-order-message-section').hide();
1594            if (cartInformation.disable_place_order) {
1595                self.hidePlaceOrderSection();
1596                $paymentFormWrapper.find('.js-place-order-message-section')
1597                    .html(cartInformation.disable_place_order_msg)
1598                    .show();
1599            }
1600
1601            // update afterpay prices
1602            flex.cart.initiateAfterPayFromContainer($checkoutForm, cartInformation.grand_total);
1603        }
1604
1605        // update afterpay prices
1606        flex.cart.initiateAfterPayFromContainer(flex.cart.getCartContainer(), cartInformation.grand_total);
1607
1608        self.showZeroPaymentMethod(cartInformation.is_zero_checkout);
1609
1610        // store last billing address for payment reference
1611        $checkoutForm.data('billing-details', cartInformation.billing_details);
1612    },
1613
1614    showZeroPaymentMethod: function(showZeroPaymentMethod) {
1615        var $methodWrapper = this.getMethodWrapperEl(),
1616            $zeroMethod = $methodWrapper.find('.payment-method-zero-checkout'),
1617            $paymentMethod = $methodWrapper.find('.payment-method'),
1618            $paymentMethodOptions = $methodWrapper.find('.payment-method-options'),
1619            $zeroMethodInput = $zeroMethod.find('[name=payment_method]'),
1620            $paymentMethodInputs = $paymentMethod.find('[name=payment_method]');
1621
1622        // if already shown don't trigger again as it will always select
1623        // the first payment method in available payment list
1624        if ((showZeroPaymentMethod && ($zeroMethod.is(":hidden") || !$zeroMethodInput.is(':checked')))
1625            || (!showZeroPaymentMethod && ($paymentMethod.is(":hidden") || $zeroMethodInput.is(':checked')))
1626        ) {
1627            $zeroMethod.hide();
1628            $paymentMethod.hide();
1629            $paymentMethodOptions.hide();
1630
1631            $paymentMethodInputs.prop('checked', false);
1632            $zeroMethodInput.prop('checked', false);
1633
1634            if (showZeroPaymentMethod) {
1635                $zeroMethodInput.prop('checked', true).trigger('change');
1636                $zeroMethod.show();
1637            } else {
1638                $paymentMethodInputs.first().prop('checked', true).trigger('change');
1639                $paymentMethod.show();
1640                $paymentMethodOptions.show();
1641            }
1642        }
1643    },
1644
1645    hidePlaceOrderSection: function() {
1646        this.getPlaceOrderSectionEl().addClass('disabled');
1647    },
1648
1649    showPlaceOrderSection: function() {
1650        this.getPlaceOrderSectionEl().removeClass('disabled');
1651    },
1652
1653    getPlaceOrderSectionEl: function() {
1654        var $wrapper = this.getWrapperEl();
1655        return $wrapper.find('.js-place-order-section');
1656    },
1657
1658    getShipDifferentAddressEl: function() {
1659        var $checkoutForm = this.getCheckoutFormEl();
1660        return $checkoutForm.find('[name=ship-different-address]');
1661    },
1662
1663    /**
1664     * Get the payment methods without the zero checkout.
1665     *
1666     * @returns {*|jQuery}
1667     */
1668    getPaymentMethodsEl: function() {
1669        var $generalForm = this.getGeneralFormEl();
1670        return $generalForm.find('.payment-method [name=payment_method]');
1671    },
1672
1673    getPaymentMethodBillingAddressContainer: function() {
1674        var $generalForm = this.getGeneralFormEl();
1675        return $generalForm.find('.js-payment-method-billing-address-container');
1676    },
1677
1678    getCheckoutFormEl: function() {
1679        return $('.js-payment-form.js-checkout-form');
1680    },
1681
1682    getGeneralFormEl: function() {
1683        return $('.js-payment-form');
1684    },
1685
1686    getWrapperEl: function() {
1687        return $('.js-payment-form-wrapper');
1688    },
1689
1690    getMethodWrapperEl: function() {
1691        return $('.js-payment-method-wrapper');
1692    },
1693
1694    updateDiscountWithLoyaltyPoints: function(action, callback) {
1695        callback = typeof callback !== 'undefined' ? callback : function() {};
1696        $loyaltyDiscountContainer = $('.js-loyalty-container');
1697        $loyaltyDiscountButton = $loyaltyDiscountContainer.find('.js-apply-loyalty');
1698        $loyaltyDiscountLoader = $loyaltyDiscountContainer.find('.js-apply-loyalty-loader');
1699        $loyaltyDiscountMsg = $loyaltyDiscountContainer.find('.js-loyalty-msg');
1700        $loyaltyDiscountMsg.hide();
1701        $loyaltyDiscountLoader.show();
1702
1703        $.ajax({
1704            type: 'POST',
1705            url: baseUrl + '/order/cart/update_discount',
1706            data: {
1707                action: action,
1708                discount_type: flexconfig.discount.DISCOUNT_SOURCE_TYPE_LOYALTY_POINTS
1709            },
1710            dataType: 'json',
1711            success: function (data) {
1712                var btnLabel = i18n.gettext('Use loyalty points');
1713                if (data.next_action == 'remove') {
1714                    btnLabel = i18n.gettext('Remove loyalty points');
1715                }
1716
1717                $loyaltyDiscountButton.attr('data-action', data.next_action);
1718                $button.text(btnLabel);
1719
1720                if (data.status == 'success') {
1721                    $loyaltyDiscountMsg.hide();
1722
1723                    flex.cart.updateCartInformation(data.cart_information);
1724                } else {
1725                    if (data.error_message) {
1726                        $loyaltyDiscountMsg.text(data.error_message);
1727                    }
1728
1729                    $loyaltyDiscountMsg.show();
1730                }
1731
1732                $loyaltyDiscountLoader.hide();
1733                callback(data);
1734            }
1735        });
1736    },
1737};
1738
1739$(function () {
1740    window.checkoutForm.init();
1741
1742    $('body').on('click', '.js-select-location', function(e) {
1743        e.preventDefault();
1744        $(this).attr('aria-expanded', 'true');
1745        flex.checkout.newLocationSelect($('.modal-location'), true);
1746    });
1747
1748    $('.modal-location').on('hidden.bs.modal', function () {
1749        $('.js-select-location[aria-expanded]').attr('aria-expanded', 'false');
1750    });
1751});

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.