PageSourceSearch

https://abidpro.org/wp-content/plugins/authorize-net-payments-for-…/js/frontend/anet-accept.js?ver=1.0.5

js abidpro.org collected 2026-10-09 22:41:38 UTC 43,803 bytes, 805 lines download raw bytes

1var { __ } = wp.i18n;
2jQuery(function ($) {
3    /**
4     * Sentry Browser SDK Initialization
5     * Activates error tracking for checkout page JavaScript errors.
6     * Configured via PHP: anetSentryConfig.dsn and anetSentryConfig.environment
7     */
8    if (typeof window.AnetSentryBrowser !== 'undefined' && typeof anetSentryConfig !== 'undefined') {
9        if (anetSentryConfig.enabled !== false && anetSentryConfig.dsn) {
10            window.AnetSentryBrowser.init({
11                dsn: anetSentryConfig.dsn,
12                environment: anetSentryConfig.environment || 'production'
13            });
14        }
15    }
16    // Utility function for XSS protection - defined at top level for reusability
17    function escapeHTML(str) {
18        if (typeof str !== 'string') return str;
19        return str
20            .replace(/&/g, "&")
21            .replace(/</g, "&lt;")
22            .replace(/>/g, "&gt;")
23            .replace(/"/g, "&quot;")
24            .replace(/'/g, "&#039;");
25    }
26    if (window.history.replaceState) {
27        window.history.replaceState(null, null, window.location.href);
28    }
29    /**
30     * Object to handle Anet payment forms.
31     */
32    var anet_accept = {
33        validationErrors: [], // Store validation error messages
34        echeckValidationErrors: [], // Store eCheck validation error messages
35        init: function () {
36            // checkout page
37            if ($('form.woocommerce-checkout').length) {
38                this.form = $('form.woocommerce-checkout');
39            }
40            //normal card
41            $('form.woocommerce-checkout')
42                .on(
43                    'checkout_place_order_anet',
44                    this.onSubmit
45                )
46            $('form.woocommerce-checkout')
47                .on(
48                    'checkout_place_order_anet_echeck',
49                    this.onSubmit
50                );
51            //pay order page
52            if ($('form#order_review').length) {
53                this.form = $('form#order_review');
54            }
55            $('form#order_review')
56                .on(
57                    'submit',
58                    this.onSubmit
59                )
60            //payment method page
61            if ($('form#add_payment_method').length) {
62                this.form = $('form#add_payment_method');
63            }
64            //store card
65            $('form#add_payment_method')
66                .on(
67                    'submit',
68                    this.onSubmit
69                )
70            $(document)
71                .on('change',
72                    '#anet-credit-card-form :input, #anet-echeck-form :input',
73                    function () {
74                        anet_accept.onCCFormChange();
75                    }
76                )
77                .on(
78                    'anetError',
79                    this.onError
80                )
81                .on(
82                    'checkout_error',
83                    this.clearToken
84                );
85            // Clear aria-invalid on focus for eCheck fields
86            $(document).on('focus', '#anet-echeck-routing-number, #anet-echeck-account-number, #anet-echeck-account-name, #anet-echeck-bank-account-type', function () {
87                $(this).removeAttr('aria-invalid');
88            });
89        },
90        isAnetPayment: function () {
91            return $('#payment_method_anet').is(':checked') && (!$('input[name="wc-anet-payment-token"]:checked').length || 'new' === $('input[name="wc-anet-payment-token"]:checked').val());
92        },
93        isAneteCheckChosen: function () {
94            return $('#payment_method_anet_echeck').is(':checked') && (!$('input[name="wc-anet_echeck-payment-token"]:checked').length || 'new' === $('input[name="wc-anet_echeck-payment-token"]:checked').val());
95        },
96        hasToken: function () {
97            return (0 < $('input.anet_nonce').length) && (0 < $('input.anet_data_descriptor').length);
98        },
99        block: function () {
100            anet_accept.form.block({
101                message: null,
102                overlayCSS: {
103                    background: '#fff',
104                    opacity: 0.6
105                }
106            });
107        },
108        unblock: function () {
109            try {
110                anet_accept.form.unblock();
111            } catch (error) {
112                console.error('Error unblocking form via form.unblock():', error);
113            }
114            // Additional aggressive unblocking for add payment method page
115            try {
116                $('form#add_payment_method').unblock();
117                $('form.woocommerce-checkout').unblock();
118                $('form#order_review').unblock();
119            } catch (error) {
120                console.error('Error unblocking individual forms:', error);
121            }
122            // Nuclear option - remove all blockUI elements
123            $('.blockUI').remove();
124            $('.blockMsg').remove();
125            $('.blockElement').removeClass('blockElement');
126            $('body').removeClass('blockUI-disabled');
127            $('form').removeClass('processing');
128        },
129        // Common error display and unblocking function
130        displayErrorAndUnblock: function (errorContent, scrollToError) {
131            scrollToError = scrollToError !== false; // Default to true
132            var errorDisplayed = false;
133            // Convert string HTML to jQuery object if needed, with sanitization
134            var $errorContent;
135            if (typeof errorContent === 'string') {
136                // For Client Potential XSS - Create element safely 
137                var cleanText = escapeHTML(errorContent);
138                $errorContent = $('<div class="woocommerce_error woocommerce-error wc-anet-error"></div>');
139                $errorContent.text(cleanText);
140            } else {
141                // Already a jQuery object
142                $errorContent = errorContent;
143            }
144            // finding the container based on form type ( CC / echeck )
145            if (anet_accept.isAnetPayment()) {  // CC errors
146                // Credit card errors - check if we're on actual checkout page
147                var checkoutWrapper = $('.woocommerce-billing-fields__field-wrapper_credit_card');
148                var isActuallyCheckoutPage = $('form.woocommerce-checkout').length > 0 && $('body').hasClass('woocommerce-checkout');
149                if (checkoutWrapper.length > 0 && checkoutWrapper.is(':visible') && isActuallyCheckoutPage) {
150                    checkoutWrapper.prepend($errorContent);
151                    errorDisplayed = true;
152                } else if ($('#anet-card-number').length > 0) {
153                    // Add payment method page - use parent container approach
154                    var parentContainer = $('#anet-card-number').closest('fieldset, .payment_box, form');
155                    if (parentContainer.length > 0) {
156                        parentContainer.prepend($errorContent);
157                        errorDisplayed = true;
158                    } else {
159                        // Fallback to form row method
160                        $('#anet-card-number').closest('.form-row').before($errorContent);
161                        errorDisplayed = true;
162                    }
163                }
164            } else if (anet_accept.isAneteCheckChosen()) {   // eCheck errors              
165                var echeckWrapper = $('.woocommerce-billing-fields__field-wrapper_echeck');
166                var isActuallyCheckoutPage = $('form.woocommerce-checkout').length > 0 && $('body').hasClass('woocommerce-checkout');
167                if (echeckWrapper.length > 0 && echeckWrapper.is(':visible') && isActuallyCheckoutPage) {
168                    echeckWrapper.prepend($errorContent);
169                    errorDisplayed = true;
170                } else if ($('#anet-echeck-routing-number').length > 0) {
171                    // Add payment method page - use parent container approach
172                    var parentContainer = $('#anet-echeck-routing-number').closest('fieldset, .payment_box, form');
173                    if (parentContainer.length > 0) {
174                        parentContainer.prepend($errorContent);
175                        errorDisplayed = true;
176                    } else {
177                        // Fallback to form row method
178                        $('#anet-echeck-routing-number').closest('.form-row').before($errorContent);
179                        errorDisplayed = true;
180                    }
181                }
182            }
183            // Error display at the top of the current form
184            if (!errorDisplayed && anet_accept.form) {
185                anet_accept.form.prepend($errorContent);
186                errorDisplayed = true;
187            }
188            // Scroll to error message
189            if (scrollToError && $('.wc-anet-error').length > 0) {
190                $('html, body').animate({
191                    scrollTop: $('.wc-anet-error').offset().top - 100
192                }, 500);
193            }
194            // Immediate unblock
195            anet_accept.unblock();
196            // Delayed additional unblock to ensure overlay is completely removed
197            setTimeout(function () {
198                $('.blockUI').remove();
199                $('.blockMsg').remove();
200                $('.blockElement').removeClass('blockElement');
201                $('body').removeClass('blockUI-disabled');
202                $('form').removeClass('processing');
203                try {
204                    $('form#add_payment_method').unblock();
205                    $('form.woocommerce-checkout').unblock();
206                    $('form#order_review').unblock();
207                } catch (error) {
208                    console.error('Delayed unblock error:', error);
209                }
210            }, 100);
211        },
212        onError: function (e, responseObject) {
213            var message = responseObject.response.text;
214            // Sentry: Track payment form errors with payment type and error code
215            if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
216                window.AnetSentryBrowser.captureMessage(
217                    'Payment form error: ' + message,
218                    'error',
219                    {
220                        error_type: 'payment_form_error',
221                        payment_type: anet_accept.isAnetPayment() ? 'credit_card' : 'echeck',
222                        extra: {
223                            response_code: responseObject.response.code || 'unknown',
224                            response_text: message
225                        }
226                    }
227                );
228            }
229            // Remove existing error messages
230            $('.wc-anet-error', '.anet_nonce').remove();
231            $('.wc-anet-error', '.anet_data_descriptor').remove();
232            // For Client Potential XSS - Create error message safely without using .html()
233            var $errorMessages = $('<div>', {
234                class: 'woocommerce_error woocommerce-error wc-anet-error'
235            });
236            // Create text node for the error message and append it
237            var errorDiv = document.createElement('div');
238            errorDiv.textContent = message;
239            $errorMessages.append(errorDiv);
240            anet_accept.displayErrorAndUnblock($errorMessages);
241        },
242        onSubmit: function (e) {
243            var authData = {};
244            var secureData = {};
245            // Check network connectivity before processing
246            if (!navigator.onLine) {
247                e.preventDefault();
248                e.stopImmediatePropagation();
249                var networkErrorMsg = __('You appear to be offline. Please check your internet connection and try again.', 'authorize-net-payments-for-woocommerce');
250                // Sentry: Track offline payment attempts (helps identify network issues)
251                if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
252                    window.AnetSentryBrowser.captureMessage(
253                        'Payment submission blocked: User offline',
254                        'warning',
255                        {
256                            error_type: 'offline_submission_blocked',
257                            payment_type: anet_accept.isAnetPayment() ? 'credit_card' : 'echeck'
258                        }
259                    );
260                }
261                anet_accept.displayErrorAndUnblock(networkErrorMsg);
262                return false;
263            }
264            if (anet_accept.isAnetPayment() && !anet_accept.hasToken()) {
265                e.preventDefault();
266                // Clear any existing errors first
267                $('.wc-anet-error').remove();
268                // Re-run all validations
269                $('[data-anet="card-number"]').trigger('blur');
270                $('[data-anet="card-cvc"]').trigger('blur');
271                $('[data-anet="expiry-month"]').trigger('blur');
272                $('[data-anet="expiry-year"]').trigger('blur');
273                // Check if any field is invalid - only for credit card fields
274                if (anet_accept.hasInvalidCreditCardFields()) {
275                    // Use specific validation errors if available
276                    var errorMessages = anet_accept.validationErrors || [__('Please correct the highlighted credit card fields.', 'authorize-net-payments-for-woocommerce')];
277                    var $errorMessages = $('<ul>', {
278                        class: 'woocommerce_error woocommerce-error wc-anet-error'
279                    });
280                    // Create list items for each error message
281                    var errorHtml = '';
282                    for (var k = 0; k < errorMessages.length; k++) {
283                        if (typeof errorMessages[k] === 'string') {
284                            errorHtml += '<li>' + escapeHTML(errorMessages[k]) + '</li>';
285                        }
286                    }
287                    $errorMessages.html(errorHtml);
288                    anet_accept.displayErrorAndUnblock($errorMessages);
289                    // Stop form submission
290                    e.preventDefault();
291                    e.stopPropagation();
292                    e.stopImmediatePropagation();
293                    return false;
294                }
295                anet_accept.block();
296                // Read card data from fields using data-anet selectors (NOT name attributes)
297                // This ensures we're explicitly reading from tokenization-only fields
298                card = $('[data-anet="card-number"]').val(),
299                    cvc = $('[data-anet="card-cvc"]').val(),
300                    expires = {
301                        month: $('[data-anet="expiry-month"]').val(),
302                        year: $('[data-anet="expiry-year"]').val()
303                    };
304                first_name = $('#billing_first_name').length ? $('#billing_first_name').val() : anet_params_cc.billing_first_name,
305                    last_name = $('#billing_last_name').length ? $('#billing_last_name').val() : anet_params_cc.billing_last_name;
306                authData.clientKey = anet_params_cc.client_key;
307                authData.apiLoginID = anet_params_cc.login_id;
308                var cardData = {};
309                cardData.cardNumber = card.replace(/\s/g, '');
310                cardData.month = expires.month.toString();
311                cardData.year = expires.year.toString().slice(-2);
312                cardData.cardCode = cvc;
313                if (first_name && last_name) {
314                    cardData.fullName = first_name + anet_params.anet_space + last_name;
315                }
316                // CVV validation
317                var amexRegex = /^3[47][0-9]{13}$/;
318                var cvvRegex = amexRegex.test(cardData.cardNumber) ? /^\d{4}$/ : /^\d{3}$/;
319                if (!cvc || !cvvRegex.test(cvc)) {
320                    $('.wc-anet-error').remove();
321                    var errorMsg = amexRegex.test(cardData.cardNumber)
322                        ? __('Please enter a valid 4-digit CVV for American Express.', 'authorize-net-payments-for-woocommerce')
323                        : __('Please enter a valid 3-digit CVV.', 'authorize-net-payments-for-woocommerce');
324                    var messageText = errorMsg;
325                    var $errorMessages = $('<ul>', {
326                        class: 'woocommerce_error woocommerce-error wc-anet-error'
327                    });
328                    // For Client Potential XSS - Create the entire HTML structure as a string with escaped content
329                    if (typeof messageText === 'string') {
330                        // Create the list item with escaped text
331                        $errorMessages.html('<li>' + escapeHTML(messageText) + '</li>');
332                    } else {
333                        console.warn('Unexpected message type:', typeof messageText, messageText);
334                        $errorMessages.html('<li>' + escapeHTML(__('An unexpected error occurred.', 'authorize-net-payments-for-woocommerce')) + '</li>'); // Safe fallback
335                    }
336                    anet_accept.displayErrorAndUnblock($errorMessages);
337                    return false;
338                }
339                secureData.authData = authData;
340                secureData.cardData = cardData;
341                // Check if Accept.js is loaded
342                if (typeof Accept === 'undefined' || typeof Accept.dispatchData !== 'function') {
343                    $('.wc-anet-error').remove();
344                    var acceptErrorMsg = __('Payment system not loaded. Please refresh the page and try again.', 'authorize-net-payments-for-woocommerce');
345                    // Sentry: Track Accept.js library loading failures (script blocked, CDN down, etc.)
346                    if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
347                        window.AnetSentryBrowser.captureMessage(
348                            'Accept.js not loaded for credit card payment',
349                            'error',
350                            {
351                                error_type: 'accept_js_not_loaded',
352                                payment_type: 'credit_card',
353                                extra: {
354                                    accept_defined: typeof Accept !== 'undefined',
355                                    accept_dispatch_defined: typeof Accept !== 'undefined' && typeof Accept.dispatchData !== 'undefined',
356                                    page_url: window.location.href
357                                }
358                            }
359                        );
360                    }
361                    anet_accept.displayErrorAndUnblock(acceptErrorMsg);
362                    return false;
363                }
364                Accept.dispatchData(secureData, anet_accept.responseHandler);
365                // Prevent form submitting
366                return false;
367            }
368            else if (anet_accept.isAneteCheckChosen() && !anet_accept.hasToken()) {
369                e.preventDefault();
370                $('.wc-anet-error').remove();
371                // Trigger validations
372                $('[data-anet="routing-number"]').trigger('blur');
373                $('[data-anet="account-number"]').trigger('blur');
374                $('[data-anet="account-name"]').trigger('blur');
375                $('[data-anet="account-type"]').trigger('blur');
376                // Check for invalid fields - only for eCheck fields
377                if (anet_accept.hasInvalidEcheckFields()) {
378                    // Use specific validation errors if available
379                    var errorMessages = anet_accept.echeckValidationErrors || [__('Please correct the highlighted eCheck fields.', 'authorize-net-payments-for-woocommerce')];
380                    var $errorMessages = $('<ul>', {
381                        class: 'woocommerce_error woocommerce-error wc-anet-error'
382                    });
383                    // Create list items for each error message
384                    var errorHtml = '';
385                    for (var k = 0; k < errorMessages.length; k++) {
386                        if (typeof errorMessages[k] === 'string') {
387                            errorHtml += '<li>' + escapeHTML(errorMessages[k]) + '</li>';
388                        }
389                    }
390                    $errorMessages.html(errorHtml);
391                    anet_accept.displayErrorAndUnblock($errorMessages);
392                    // Stop form submission
393                    e.preventDefault();
394                    e.stopPropagation();
395                    e.stopImmediatePropagation();
396                    return false;
397                }
398                anet_accept.block();
399                // Read eCheck data from fields using data-anet selectors (NOT name attributes)
400                // This ensures we're explicitly reading from tokenization-only fields
401                var routing_number = $('[data-anet="routing-number"]').val(),
402                    account_number = $('[data-anet="account-number"]').val(),
403                    account_name = $('[data-anet="account-name"]').val(),
404                    account_type = $('[data-anet="account-type"]').val();
405                first_name = $('#billing_first_name').length ? $('#billing_first_name').val() : anet_echeck_params.billing_first_name,
406                    last_name = $('#billing_last_name').length ? $('#billing_last_name').val() : anet_echeck_params.billing_last_name;
407                authData.clientKey = anet_echeck_params.client_key;
408                authData.apiLoginID = anet_echeck_params.login_id;
409                var bankData = {};
410                bankData.accountNumber = account_number;
411                bankData.routingNumber = routing_number;
412                bankData.nameOnAccount = account_name;
413                bankData.accountType = account_type;
414                if (bankData.nameOnAccount === '' && first_name && last_name) {
415                    bankData.nameOnAccount = first_name + anet_params.anet_space + last_name;
416                }
417                secureData.authData = authData;
418                secureData.bankData = bankData;
419                // Check if Accept.js is loaded
420                if (typeof Accept === 'undefined' || typeof Accept.dispatchData !== 'function') {
421                    $('.wc-anet-error').remove();
422                    var acceptErrorMsg = __('Payment system not loaded. Please refresh the page and try again.', 'authorize-net-payments-for-woocommerce');
423                    // Sentry: Track Accept.js library loading failures for eCheck
424                    if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
425                        window.AnetSentryBrowser.captureMessage(
426                            'Accept.js not loaded for eCheck payment',
427                            'error',
428                            {
429                                error_type: 'accept_js_not_loaded',
430                                payment_type: 'echeck',
431                                extra: {
432                                    accept_defined: typeof Accept !== 'undefined',
433                                    accept_dispatch_defined: typeof Accept !== 'undefined' && typeof Accept.dispatchData !== 'undefined',
434                                    page_url: window.location.href
435                                }
436                            }
437                        );
438                    }
439                    anet_accept.displayErrorAndUnblock(acceptErrorMsg);
440                    return false;
441                }
442                Accept.dispatchData(secureData, anet_accept.responseHandler);
443                // Prevent form submitting
444                return false;
445            }
446            else {
447                // Allow normal form submission for saved payment methods or other scenarios
448                return true;
449            }
450        },
451        onCCFormChange: function () {
452            $('.wc-anet-error, .anet_nonce').remove();
453            $('.wc-anet-error, .anet_data_descriptor').remove();
454        },
455        responseHandler: function (response) {
456            if (anet_params.anet_error === response.messages.resultCode) {
457                // Handle errors from Accept.js
458                var i = 0;
459                // Collect all error messages first
460                var errorMessagesArray = [];
461                var errorCodes = [];
462                while (i < response.messages.message.length) {
463                    console.error('Accept.js API Error:', response.messages.message[i].code + ": " + response.messages.message[i].text);
464                    var messageText = response.messages.message[i].text;
465                    var errorCode = response.messages.message[i].code;
466                    errorCodes.push(errorCode);
467                    // For Client Potential XSS - Collect escaped messages in an array
468                    if (typeof messageText === 'string') {
469                        errorMessagesArray.push(escapeHTML(messageText));
470                    } else {
471                        console.warn('Unexpected message type:', typeof messageText, messageText);
472                        errorMessagesArray.push('An unexpected error occurred.');
473                    }
474                    i = i + 1;
475                }
476                // Sentry: Track tokenization errors from Authorize.net API (invalid card, expired, etc.)
477                if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
478                    window.AnetSentryBrowser.captureMessage(
479                        'Accept.js API Error: ' + errorMessagesArray.join(', '),
480                        'error',
481                        {
482                            error_type: 'accept_js_api_error',
483                            payment_type: anet_accept.isAnetPayment() ? 'credit_card' : 'echeck',
484                            extra: {
485                                error_codes: errorCodes.join(', '),
486                                error_messages: errorMessagesArray,
487                                result_code: response.messages.resultCode
488                            }
489                        }
490                    );
491                }
492                // Create the error messages container
493                var $errorMessages = $('<div>', {
494                    class: 'woocommerce_error woocommerce-error wc-anet-error'
495                });
496                // Create text nodes for each error message and append them
497                for (var j = 0; j < errorMessagesArray.length; j++) {
498                    var errorDiv = document.createElement('div');
499                    errorDiv.textContent = errorMessagesArray[j];
500                    $errorMessages.append(errorDiv);
501                }
502                // Use centralized error handling for API errors
503                anet_accept.displayErrorAndUnblock($errorMessages, 'API validation error');
504                return false;
505            }
506            else {
507                // Handle successful tokenization
508                anet_accept.paymentFormUpdate(response.opaqueData);
509            }
510        },
511        paymentFormUpdate: function (opaqueData) {
512            // Insert the token into the form so it gets submitted to the server
513            var $nonceInput = $('<input>', {
514                type: 'hidden',
515                class: 'anet_nonce',
516                name: 'anet_nonce',
517                value: opaqueData.dataValue
518            });
519            var $descriptorInput = $('<input>', {
520                type: 'hidden',
521                class: 'anet_data_descriptor',
522                name: 'anet_data_descriptor',
523                value: opaqueData.dataDescriptor
524            });
525            anet_accept.form.append($nonceInput);
526            anet_accept.form.append($descriptorInput);
527            // Network monitoring for form submission
528            anet_accept.startNetworkMonitoring();
529            // Submit the form
530            anet_accept.form.submit();
531        },
532        // Network monitoring - covers all scenarios
533        startNetworkMonitoring: function () {
534            var self = this;
535            // Listen for network offline event
536            window.addEventListener('offline', this.networkHandler = function () {
537                self.handleNetworkIssue(__('Connection lost during processing - please check your internet connection.', 'authorize-net-payments-for-woocommerce'));
538            });
539        },
540        // Handle any network issue with cleanup and error reporting
541        handleNetworkIssue: function (message) {
542            // Cleanup event listener to prevent memory leaks
543            if (this.networkHandler) {
544                window.removeEventListener('offline', this.networkHandler);
545                this.networkHandler = null;
546            }
547            // Sentry: Track network failures during payment processing (connection lost mid-transaction)
548            if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
549                window.AnetSentryBrowser.captureMessage(
550                    'Network issue during payment: ' + message,
551                    'error',
552                    {
553                        error_type: 'network_error',
554                        payment_type: anet_accept.isAnetPayment() ? 'credit_card' : 'echeck',
555                        extra: {
556                            online_status: navigator.onLine,
557                            user_agent: navigator.userAgent,
558                            page_url: window.location.href
559                        }
560                    }
561                );
562            }
563            anet_accept.displayErrorAndUnblock(message);
564        },
565        clearToken: function () {
566            $('.anet_nonce').remove();
567            $('.anet_data_descriptor').remove();
568        },
569        // Function to detect card type from card number
570        getCardType: function (cardNumber) {
571            var cleanNumber = cardNumber.replace(/\D/g, '');
572            // Visa
573            if (/^4/.test(cleanNumber)) {
574                return 'visa';
575            }
576            // Mastercard
577            if (/^5[1-5]|^2[2-7]/.test(cleanNumber)) {
578                return 'mastercard';
579            }
580            // American Express
581            if (/^3[47]/.test(cleanNumber)) {
582                return 'amex';
583            }
584            // Discover
585            if (/^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/.test(cleanNumber)) {
586                return 'discover';
587            }
588            // JCB
589            if (/^35(2[89]|[3-8][0-9])/.test(cleanNumber)) {
590                return 'jcb';
591            }
592            // Diners Club
593            if (/^3[0689]/.test(cleanNumber)) {
594                return 'diners-club';
595            }
596            return 'unknown';
597        },
598        // Function to check if card type is allowed
599        isCardTypeAllowed: function (cardType, allowedTypes) {
600            if (!allowedTypes || 0 === allowedTypes.length) {
601                return false; // If no card types configured, don't allow any
602            }
603            return allowedTypes.indexOf(cardType) !== -1;
604        },
605        // Function to check for invalid credit card fields only
606        hasInvalidCreditCardFields: function () {
607            var isValid = true;
608            var errors = [];
609            // Check card number (12-16 digits)
610            var cardNumber = $('[data-anet="card-number"]').val();
611            var cleanCardNumber = cardNumber ? cardNumber.replace(/\s/g, '') : '';
612            if (!cleanCardNumber || !/^\d{12,16}$/.test(cleanCardNumber)) {
613                $('#anet-card-number-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
614                $('#anet-card-number-div').removeClass('woocommerce-validated');
615                errors.push(__('Please enter a valid credit card number (12-16 digits).', 'authorize-net-payments-for-woocommerce'));
616                isValid = false;
617            } else {
618                // Additional validation using Luhn algorithm
619                if (!anet_accept.isValidCardNumber(cleanCardNumber)) {
620                    $('#anet-card-number-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
621                    $('#anet-card-number-div').removeClass('woocommerce-validated');
622                    errors.push(__('Please enter a valid credit card number.', 'authorize-net-payments-for-woocommerce'));
623                    isValid = false;
624                } else {
625                    // Card type validation - check if card type is allowed
626                    var cardType = anet_accept.getCardType(cleanCardNumber);
627                    var allowedCardTypes = anet_params_cc.allowed_card_types || [];
628                    if (!anet_accept.isCardTypeAllowed(cardType, allowedCardTypes)) {
629                        $('#anet-card-number-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
630                        $('#anet-card-number-div').removeClass('woocommerce-validated');
631                        if (!allowedCardTypes || allowedCardTypes.length === 0) {
632                            errors.push(__('No card types have been configured in admin. Please contact support.', 'authorize-net-payments-for-woocommerce'));
633                        } else {
634                            errors.push(__('This card type is not accepted. Please use a different card.', 'authorize-net-payments-for-woocommerce'));
635                        }
636                        isValid = false;
637                    } else {
638                        $('#anet-card-number-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
639                        $('#anet-card-number-div').addClass('woocommerce-validated');
640                    }
641                }
642            }
643            // Check CVV
644            var cvc = $('[data-anet="card-cvc"]').val();
645            var amexRegex = /^3[47][0-9]{13}$/;
646            var expectedCvcLength = amexRegex.test(cleanCardNumber) ? 4 : 3;
647            var cvcRegex = new RegExp('^\\d{' + expectedCvcLength + '}$');
648            if (!cvc || !cvcRegex.test(cvc)) {
649                $('#anet-card-cvc-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
650                $('#anet-card-cvc-div').removeClass('woocommerce-validated');
651                var cvcErrorMsg = amexRegex.test(cleanCardNumber)
652                    ? __('Please enter a valid 4-digit CVV for American Express.', 'authorize-net-payments-for-woocommerce')
653                    : __('Please enter a valid 3-digit CVV.', 'authorize-net-payments-for-woocommerce');
654                errors.push(cvcErrorMsg);
655                isValid = false;
656            } else {
657                $('#anet-card-cvc-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
658                $('#anet-card-cvc-div').addClass('woocommerce-validated');
659            }
660            // Check expiry month and year together to prevent past dates
661            var expiryMonth = $('[data-anet="expiry-month"]').val();
662            var expiryYear = $('[data-anet="expiry-year"]').val();
663            var currentDate = new Date();
664            var currentYear = currentDate.getFullYear();
665            var currentMonth = currentDate.getMonth() + 1; // getMonth() returns 0-11, so add 1
666            var monthValue = parseInt(expiryMonth);
667            var yearValue = parseInt(expiryYear);
668            // Handle 2-digit years
669            if (yearValue < 100) {
670                yearValue += 2000;
671            }
672            // Validate month range
673            if (!expiryMonth || expiryMonth === '' || monthValue < 1 || monthValue > 12) {
674                $('#anet-card-expiry-month-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
675                $('#anet-card-expiry-month-div').removeClass('woocommerce-validated');
676                errors.push(__('Please select a valid expiry month.', 'authorize-net-payments-for-woocommerce'));
677                isValid = false;
678            }
679            // Validate year range
680            if (!expiryYear || expiryYear === '' || yearValue < currentYear || yearValue > currentYear + 20) {
681                $('#anet-card-expiry-year-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
682                $('#anet-card-expiry-year-div').removeClass('woocommerce-validated');
683                if (yearValue < currentYear) {
684                    errors.push(__('Expiry year cannot be in the past.', 'authorize-net-payments-for-woocommerce'));
685                } else if (yearValue > currentYear + 20) {
686                    errors.push(__('Expiry year is too far in the future.', 'authorize-net-payments-for-woocommerce'));
687                } else {
688                    errors.push(__('Please select a valid expiry year.', 'authorize-net-payments-for-woocommerce'));
689                }
690                isValid = false;
691            }
692            // If both month and year are valid individually, check if the combination is in the past
693            if (expiryMonth && expiryYear && monthValue >= 1 && monthValue <= 12 && yearValue >= currentYear) {
694                // Check if the expiry date is in the past
695                if (yearValue < currentYear || (yearValue === currentYear && monthValue < currentMonth)) {
696                    $('#anet-card-expiry-month-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
697                    $('#anet-card-expiry-year-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
698                    $('#anet-card-expiry-month-div').removeClass('woocommerce-validated');
699                    $('#anet-card-expiry-year-div').removeClass('woocommerce-validated');
700                    errors.push(__('Credit card expiry date cannot be in the past.', 'authorize-net-payments-for-woocommerce'));
701                    isValid = false;
702                } else {
703                    // Both month and year are valid and not in the past
704                    $('#anet-card-expiry-month-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
705                    $('#anet-card-expiry-month-div').addClass('woocommerce-validated');
706                    $('#anet-card-expiry-year-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
707                    $('#anet-card-expiry-year-div').addClass('woocommerce-validated');
708                }
709            }
710            // Store errors for later use
711            anet_accept.validationErrors = errors;
712            return !isValid;
713        },
714        // Luhn algorithm validation for credit card numbers
715        isValidCardNumber: function (cardNumber) {
716            var sum = 0;
717            var alternate = false;
718            for (var i = cardNumber.length - 1; i >= 0; i--) {
719                var digit = parseInt(cardNumber.charAt(i), 10);
720                if (alternate) {
721                    digit *= 2;
722                    if (digit > 9) {
723                        digit = (digit % 10) + 1;
724                    }
725                }
726                sum += digit;
727                alternate = !alternate;
728            }
729            return (sum % 10 === 0);
730        },
731        // Function to check for invalid eCheck fields only
732        hasInvalidEcheckFields: function () {
733            var isValid = true;
734            var errors = [];
735            // Check routing number
736            var routingNumber = $('[data-anet="routing-number"]').val();
737            if (!routingNumber || !/^\d{9}$/.test(routingNumber)) {
738                $('#anet-bank-routing-number-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
739                $('#anet-bank-routing-number-div').removeClass('woocommerce-validated');
740                $('#anet-echeck-routing-number').attr('aria-invalid', 'true');
741                if (!routingNumber) {
742                    errors.push(__('Please enter your bank routing number.', 'authorize-net-payments-for-woocommerce'));
743                } else {
744                    errors.push(__('Routing number must be exactly 9 digits.', 'authorize-net-payments-for-woocommerce'));
745                }
746                isValid = false;
747            } else {
748                $('#anet-bank-routing-number-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
749                $('#anet-bank-routing-number-div').addClass('woocommerce-validated');
750                $('#anet-echeck-routing-number').attr('aria-invalid', 'false');
751            }
752            // Check account number
753            var accountNumber = $('[data-anet="account-number"]').val();
754            if (!accountNumber || !/^\d{5,17}$/.test(accountNumber)) {
755                $('#anet-bank-account-number-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
756                $('#anet-bank-account-number-div').removeClass('woocommerce-validated');
757                $('#anet-echeck-account-number').attr('aria-invalid', 'true');
758                if (!accountNumber) {
759                    errors.push(__('Please enter your bank account number.', 'authorize-net-payments-for-woocommerce'));
760                } else {
761                    errors.push(__('Account number must be between 5-17 digits.', 'authorize-net-payments-for-woocommerce'));
762                }
763                isValid = false;
764            } else {
765                $('#anet-bank-account-number-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
766                $('#anet-bank-account-number-div').addClass('woocommerce-validated');
767                $('#anet-echeck-account-number').attr('aria-invalid', 'false');
768            }
769            // Check account name
770            var accountName = $('[data-anet="account-name"]').val();
771            if (!accountName || !/^[a-zA-Z\s]{2,}$/.test(accountName)) {
772                $('#anet-bank-account-name-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
773                $('#anet-bank-account-name-div').removeClass('woocommerce-validated');
774                $('#anet-echeck-account-name').attr('aria-invalid', 'true');
775                if (!accountName) {
776                    errors.push(__('Please enter the name on the account.', 'authorize-net-payments-for-woocommerce'));
777                } else {
778                    errors.push(__('Account name must contain only letters and spaces, minimum 2 characters.', 'authorize-net-payments-for-woocommerce'));
779                }
780                isValid = false;
781            } else {
782                $('#anet-bank-account-name-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
783                $('#anet-bank-account-name-div').addClass('woocommerce-validated');
784                $('#anet-echeck-account-name').attr('aria-invalid', 'false');
785            }
786            // Check account type
787            var accountType = $('[data-anet="account-type"]').val();
788            if (!accountType || accountType === '') {
789                $('#anet-bank-account-type-div').addClass('woocommerce-invalid woocommerce-invalid-required-field');
790                $('#anet-bank-account-type-div').removeClass('woocommerce-validated');
791                $('#anet-echeck-bank-account-type').attr('aria-invalid', 'true');
792                errors.push(__('Please select an account type.', 'authorize-net-payments-for-woocommerce'));
793                isValid = false;
794            } else {
795                $('#anet-bank-account-type-div').removeClass('woocommerce-invalid woocommerce-invalid-required-field');
796                $('#anet-bank-account-type-div').addClass('woocommerce-validated');
797                $('#anet-echeck-bank-account-type').attr('aria-invalid', 'false');
798            }
799            // Store errors for later use
800            anet_accept.echeckValidationErrors = errors;
801            return !isValid;
802        }
803    };
804    anet_accept.init();
805});

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.