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, "<") 22 .replace(/>/g, ">") 23 .replace(/"/g, """) 24 .replace(/'/g, "'"); 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.