// API base URL const API_BASE = '/api/v1/client'; // Format empty fields function formatEmptyFields() { const inputs = document.querySelectorAll('.form-input'); inputs.forEach(input => { if (!input.value.trim()) { input.value = 'Not provided'; input.classList.add('empty-field'); } else { input.classList.remove('empty-field'); } }); } // Load profile data async function loadProfileData() { try { const response = await fetchWithAuth(`${API_BASE}/profile`); if (!response) return; const result = await response.json(); if (result.success) { const data = result.data; // Populate only personal and address information const fieldsToShow = ['name', 'email', 'position', 'address', 'city', 'state', 'zip']; fieldsToShow.forEach(key => { const element = document.getElementById(key); if (element && data[key]) { element.value = data[key]; } }); formatEmptyFields(); } else { console.error('Failed to load profile data:', result.message); alert('Failed to load profile data'); } } catch (error) { console.error('Error loading profile data:', error); alert('Error loading profile data'); } } // Password Modal Functions function openPasswordModal() { const modal = document.getElementById('passwordModal'); modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // Clear form document.getElementById('passwordForm').reset(); clearErrorMessages(); } function closePasswordModal() { const modal = document.getElementById('passwordModal'); modal.style.display = 'none'; document.body.style.overflow = 'auto'; // Clear form and errors document.getElementById('passwordForm').reset(); clearErrorMessages(); } // Bank Details Modal Functions function openBankDetailsModal() { const modal = document.getElementById('bankDetailsModal'); modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // Clear form and errors document.getElementById('payment-form').reset(); document.querySelectorAll('.payment-option').forEach(option => { option.classList.remove('selected'); }); document.querySelectorAll('.payment-details').forEach(details => { details.classList.remove('active'); // Remove required attributes from all payment fields details.querySelectorAll('[required]').forEach(field => { field.removeAttribute('required'); }); }); } function closeBankDetailsModal() { const modal = document.getElementById('bankDetailsModal'); modal.style.display = 'none'; document.body.style.overflow = 'auto'; // Clear form and errors document.getElementById('payment-form').reset(); document.querySelectorAll('.payment-option').forEach(option => { option.classList.remove('selected'); }); document.querySelectorAll('.payment-details').forEach(details => { details.classList.remove('active'); // Remove required attributes from all payment fields details.querySelectorAll('[required]').forEach(field => { field.removeAttribute('required'); }); }); } function clearErrorMessages() { document.getElementById('currentPasswordError').textContent = ''; document.getElementById('newPasswordError').textContent = ''; document.getElementById('confirmPasswordError').textContent = ''; } // Change password async function changePassword(event) { event.preventDefault(); const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; // Reset errors clearErrorMessages(); let hasError = false; if (!currentPassword) { document.getElementById('currentPasswordError').textContent = 'Current password is required'; hasError = true; } if (!newPassword) { document.getElementById('newPasswordError').textContent = 'New password is required'; hasError = true; } else if (newPassword.length < 6) { document.getElementById('newPasswordError').textContent = 'Password must be at least 6 characters'; hasError = true; } if (!confirmPassword) { document.getElementById('confirmPasswordError').textContent = 'Please confirm your password'; hasError = true; } else if (newPassword !== confirmPassword) { document.getElementById('confirmPasswordError').textContent = 'Passwords do not match'; hasError = true; } if (hasError) return; try { const saveBtn = document.getElementById('savePasswordBtn'); saveBtn.disabled = true; saveBtn.textContent = 'Changing Password...'; const response = await fetchWithAuth(`${API_BASE}/profile`, { method: 'PATCH', body: JSON.stringify({ userDetails: { currentPassword, newPassword, confirmPassword } }) }); if (!response) { saveBtn.disabled = false; saveBtn.textContent = 'Change Password'; return; } const result = await response.json(); if (result.success) { alert('Password changed successfully!'); closePasswordModal(); } else { alert(result.message || 'Failed to change password'); } } catch (error) { console.error('Error changing password:', error); alert('Error changing password'); } finally { const saveBtn = document.getElementById('savePasswordBtn'); saveBtn.disabled = false; saveBtn.textContent = 'Change Password'; } } // Change bank details async function changeBankDetails(event) { event.preventDefault(); const formData = { userDetails: {} }; const activePaymentDetails = document.querySelector('.payment-details.active'); if (activePaymentDetails) { const requiredFields = activePaymentDetails.querySelectorAll('[required]'); let isValid = true; formData.userDetails.paymentMethod = activePaymentDetails.id === 'card-details' ? 'card' : 'bank'; // Validate card payment if (formData.userDetails.paymentMethod === 'card') { // Card number validation const cardNumber = document.getElementById('card-number').value; if (!validateCardNumber(cardNumber)) { isValid = false; showBankError('card-number'); return isValid; } else { // Show card type const cardType = detectCardType(cardNumber); if (cardType !== 'unknown') { // You can display card type to user if needed console.log('Detected card type:', cardType); } } // Expiry date validation const expiryDate = document.getElementById('expiry-date').value; if (!validateExpiryDate(expiryDate)) { isValid = false; showBankError('expiry-date'); return isValid; } // CVV validation const cvv = document.getElementById('security-code').value; const cardType = detectCardType(cardNumber); if (!validateCVV(cvv, cardType)) { isValid = false; showBankError('security-code'); return isValid; } // ZIP code validation const zipCode = document.getElementById('card-zip').value; if (!validateZipCodeRegex(zipCode)) { isValid = false; showBankError('card-zip'); return isValid; } } // Validate bank payment else if (formData.userDetails.paymentMethod === 'bank') { // Account number validation const accountNumber = document.getElementById('account-number').value; if (!validateBankAccount(accountNumber)) { isValid = false; showBankError('account-number'); return isValid; } // Routing number validation const routingNumber = document.getElementById('routing-number').value; if (!validateRoutingNumber(routingNumber)) { isValid = false; showBankError('routing-number'); return isValid; } // Basic validation for other bank fields requiredFields.forEach(field => { if (!field.value.trim()) { isValid = false; field.classList.add('field-error'); if (isValid === false) { showBankError(field.id); } } else { field.classList.remove('field-error'); } }); } } let accountCardNumber = document.getElementById('card-number'); let accountExpiryDate = document.getElementById('expiry-date'); let accountSecurityCode = document.getElementById('security-code'); let accountCardZip = document.getElementById('card-zip'); let accountBankName = document.getElementById('bank-name'); let accountAccountNumber = document.getElementById('account-number'); let accountRoutingNumber = document.getElementById('routing-number'); let accountAccountType = document.getElementById('account-type'); let accountAccountHolderName = document.getElementById('account-holder-name'); if(accountCardNumber.value) { formData.userDetails.cardNumber = accountCardNumber.value; } if(accountExpiryDate.value) { formData.userDetails.expiryDate = accountExpiryDate.value; } if(accountSecurityCode.value) { formData.userDetails.securityCode = accountSecurityCode.value; } if(accountCardZip.value) { formData.userDetails.cardZip = accountCardZip.value; } if(accountBankName.value) { formData.userDetails.bankName = accountBankName.value; } if(accountAccountNumber.value) { formData.userDetails.accountNumber = accountAccountNumber.value; } if(accountRoutingNumber.value) { formData.userDetails.routingNumber = accountRoutingNumber.value; } if(accountAccountType.value) { formData.userDetails.accountType = accountAccountType.value; } if(accountAccountHolderName.value) { formData.userDetails.accountHolderName = accountAccountHolderName.value; } try { const saveBtn = document.getElementById('saveBankDetailsBtn'); saveBtn.disabled = true; saveBtn.textContent = 'Updatind Details...'; const response = await fetchWithAuth(`${API_BASE}/profile`, { method: 'POST', body: JSON.stringify(formData) }); if (!response) { saveBtn.disabled = false; saveBtn.textContent = 'Update Bank Details'; return; } const result = await response.json(); if (result.success) { alert('Bank details updated successfully!'); closeBankDetailsModal(); } else { alert(result.message || 'Failed to update bank details'); } } catch (error) { console.error('Error changing Bank Details:', error); alert('Error changing bank details'); } finally { const saveBtn = document.getElementById('saveBankDetailsBtn'); saveBtn.disabled = false; saveBtn.textContent = 'Update Bank Details'; } } // Bank account validation function validateBankAccount(accountNumber) { return /^\d{8,17}$/.test(accountNumber.replace(/\s/g, '')); } // Routing number validation (basic US routing number check) function validateRoutingNumber(routingNumber) { const cleanRouting = routingNumber.replace(/\s/g, ''); if (!/^\d{9}$/.test(cleanRouting)) { return false; } // Check for invalid patterns if (isInvalidRoutingNumberPattern(cleanRouting)) { return false; } // ABA routing number checksum validation return isValidABAChecksum(cleanRouting); } function isInvalidRoutingNumberPattern(routingNumber) { const invalidPatterns = [ /^0+$/, // All zeros /^1+$/, // All ones /^2+$/, // All twos /^3+$/, // All threes /^4+$/, // All fours /^5+$/, // All fives /^6+$/, // All sixes /^7+$/, // All sevens /^8+$/, // All eights /^9+$/, // All nines /^012345678/, // Sequential /^876543210/, // Reverse sequential ]; return invalidPatterns.some(pattern => pattern.test(routingNumber)); } function isValidABAChecksum(routingNumber) { const digits = routingNumber.split('').map(Number); // ABA routing number checksum formula const sum = (3 * (digits[0] + digits[3] + digits[6])) + (7 * (digits[1] + digits[4] + digits[7])) + (1 * (digits[2] + digits[5] + digits[8])); return sum % 10 === 0; } // Expiry date validation function validateExpiryDate(expiryDate) { const expiryRegex = /^(0[1-9]|1[0-2])\/([0-9]{2})$/; if (!expiryRegex.test(expiryDate)) { return false; } const [month, year] = expiryDate.split('/'); const expiry = new Date(2000 + parseInt(year), parseInt(month)); const currentDate = new Date(); const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth()); return expiry >= currentMonth; } // CVV validation function validateCVV(cvv, cardType) { if (!/^\d+$/.test(cvv)) { return false; } const cvvLengths = { visa: 3, mastercard: 3, amex: 4, discover: 3, diners: 3, jcb: 3 }; const expectedLength = cvvLengths[cardType] || 3; return cvv.length === expectedLength; } // ZIP code validation function validateZipCodeRegex(zipCode) { return /^\d{5}(-\d{4})?$/.test(zipCode); } function validateCardNumber(cardNumber) { const cleanNumber = cardNumber.replace(/[\s-]/g, ''); // Check if it's all digits and has proper length if (!/^\d+$/.test(cleanNumber) || cleanNumber.length < 13 || cleanNumber.length > 19) { return false; } // Check for invalid patterns if (isInvalidCardPattern(cleanNumber)) { return false; } // Check for invalid BIN ranges if (hasInvalidBIN(cleanNumber)) { return false; } // Luhn algorithm return isValidLuhn(cleanNumber); } // Enhanced Luhn algorithm function isValidLuhn(cardNumber) { let sum = 0; let isEven = false; for (let i = cardNumber.length - 1; i >= 0; i--) { let digit = parseInt(cardNumber.charAt(i)); if (isEven) { digit *= 2; if (digit > 9) { digit -= 9; } } sum += digit; isEven = !isEven; } return sum % 10 === 0; } // Function to check for invalid card patterns function isInvalidCardPattern(cardNumber) { const invalidPatterns = [ /^0+$/, // All zeros /^1+$/, // All ones /^2+$/, // All twos /^3+$/, // All threes /^4+$/, // All fours /^5+$/, // All fives /^6+$/, // All sixes /^7+$/, // All sevens /^8+$/, // All eights /^9+$/, // All nines /^0123/, // Sequential /^1234/, // Sequential ]; return invalidPatterns.some(pattern => pattern.test(cardNumber)); } // Function to check for invalid BIN ranges function hasInvalidBIN(cardNumber) { const firstSix = cardNumber.substring(0, 6); const firstFour = cardNumber.substring(0, 4); const firstTwo = cardNumber.substring(0, 2); // Invalid BIN ranges const invalidBINs = [ '000000', '000001', '000002', '000003', // Test cards should be handled separately '111111', '222222', '333333', '444444', '555555', '666666', '777777', '888888', '999999', '123456', '654321' ]; // Check specific invalid ranges if (invalidBINs.includes(firstSix)) { return true; } // Check for known invalid starting digits if (['0', '3', '6', '7', '8'].includes(firstTwo)) { // 0 - Reserved // 3 - Should only be 34, 37 (AMEX) // 6 - Should follow Discover patterns // 7, 8 - Not currently assigned to major networks if (!isValidCardPrefix(cardNumber)) { return true; } } return false; } function isValidCardPrefix(cardNumber) { const type = detectCardType(cardNumber); return type !== 'unknown'; } // Event listeners document.addEventListener('DOMContentLoaded', function () { loadProfileData(); // Change password button document.getElementById('changePasswordBtn').addEventListener('click', openPasswordModal); document.getElementById('updateBankDetailsBtn').addEventListener('click', openBankDetailsModal); // Password form submission document.getElementById('passwordForm').addEventListener('submit', changePassword); document.getElementById('payment-form').addEventListener('submit', changeBankDetails); // Close modal when clicking outside document.getElementById('passwordModal').addEventListener('click', function (e) { if (e.target === this) { closePasswordModal(); } }); document.getElementById('bankDetailsModal').addEventListener('click', function(e) { if (e.target === this) { closeBankDetailsModal(); } }); // Close modal with escape key document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { closePasswordModal(); closeLogoutModal(); closeBankDetailsModal(); } }); document.getElementById('logoutModal').addEventListener('click', function (e) { if (e.target === this) { closeLogoutModal(); } }); // Logout button - opens confirmation modal instead of directly logging out document.getElementById('logoutButton').addEventListener('click', openLogoutModal); // Confirm logout button document.getElementById('confirmLogoutBtn').addEventListener('click', handleLogout); // Mobile menu toggle const menuToggle = document.querySelector('.mobile-menu-button'); const sidebar = document.querySelector('.dashboard-sidebar'); menuToggle.addEventListener('click', () => { sidebar.classList.toggle('active'); }); }); // Function to select payment option function selectPaymentOption(type) { // Remove selected class from all options document.querySelectorAll('.payment-option').forEach(option => { option.classList.remove('selected'); }); document.querySelectorAll('.payment-details').forEach(details => { details.classList.remove('active'); // Remove required attributes from all payment fields details.querySelectorAll('[required]').forEach(field => { field.removeAttribute('required'); }); }); // Add selected class to clicked option event.currentTarget.classList.add('selected'); if (type === 'card') { document.getElementById('card-details').classList.add('active'); // Add required attributes only to card fields document.getElementById('card-number').setAttribute('required', 'true'); document.getElementById('expiry-date').setAttribute('required', 'true'); document.getElementById('security-code').setAttribute('required', 'true'); document.getElementById('card-zip').setAttribute('required', 'true'); } else if (type === 'bank') { document.getElementById('bank-details').classList.add('active'); // Add required attributes only to bank fields document.getElementById('bank-name').setAttribute('required', 'true'); document.getElementById('account-number').setAttribute('required', 'true'); document.getElementById('routing-number').setAttribute('required', 'true'); document.getElementById('account-type').setAttribute('required', 'true'); document.getElementById('account-holder-name').setAttribute('required', 'true'); } } // Bank Name validation function validateBankName(input) { const errorElement = document.getElementById('bank-name-error'); const value = input.value; // Remove numbers and invalid special characters const cleanValue = value.replace(/[^a-zA-Z\s\-'&.,()]/g, ''); if (value !== cleanValue) { input.value = cleanValue; } // Check content const contentCheck = value.replace(/[\s\-'&.,()]/g, ''); const hasOnlyNumbers = /^[0-9]+$/.test(contentCheck) && contentCheck.length > 0; const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(contentCheck) && contentCheck.length > 0; const hasLetters = /[a-zA-Z]/.test(value); if (value !== cleanValue && value.length > 0) { errorElement.textContent = 'Bank name can only contain letters, spaces, and common punctuation'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlyNumbers) { errorElement.textContent = 'Bank name cannot be only numbers'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlySpecialChars) { errorElement.textContent = 'Bank name cannot be only special characters'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (!hasLetters && value.trim().length > 0) { errorElement.textContent = 'Bank name must include letters'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function preventInvalidBankNameChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122) || charCode === 32 || charCode === 45 || charCode === 39 || charCode === 38 || charCode === 46 || charCode === 44 || charCode === 40 || charCode === 41 || charCode === 8 || charCode === 9 || charCode === 37 || charCode === 39 || charCode === 46 || charCode === 16 || charCode === 17 || charCode === 18 || charCode === 91 || /^[a-zA-Z\s\-'&.,()]$/.test(key)) { return true; } event.preventDefault(); return false; } // Enhanced Account Number validation function validateAccountNumber(input) { const errorElement = document.getElementById('account-number-error'); const value = input.value.replace(/\s/g, ''); // Remove any non-digit characters const cleanValue = value.replace(/\D/g, ''); if (value !== cleanValue) { input.value = cleanValue; } // Validate account number const isValid = validateAccountNumberStrict(cleanValue); if (!isValid && cleanValue.length > 0) { errorElement.textContent = getAccountNumberErrorMessage(cleanValue); errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function preventInvalidAccountNumberChars(event) { const charCode = event.which ? event.which : event.keyCode; if ((charCode >= 48 && charCode <= 57) || charCode === 8 || charCode === 9 || charCode === 37 || charCode === 39 || charCode === 46 || charCode === 16 || charCode === 17 || charCode === 18 || charCode === 91) { return true; } event.preventDefault(); return false; } function validateAccountNumberStrict(accountNumber) { if (!accountNumber || !/^\d+$/.test(accountNumber)) { return false; } // Check length if (accountNumber.length < 8 || accountNumber.length > 17) { return false; } // Check for invalid patterns if (isInvalidAccountNumberPattern(accountNumber)) { return false; } // Simple checksum validation (basic example) if (!isValidAccountNumberChecksum(accountNumber)) { return false; } return true; } function isInvalidAccountNumberPattern(accountNumber) { const invalidPatterns = [ /^0+$/, // All zeros /^1+$/, // All ones /^2+$/, // All twos /^3+$/, // All threes /^4+$/, // All fours /^5+$/, // All fives /^6+$/, // All sixes /^7+$/, // All sevens /^8+$/, // All eights /^9+$/, // All nines /^12345678/, // Sequential /^87654321/, // Reverse sequential ]; return invalidPatterns.some(pattern => pattern.test(accountNumber)); } function isValidAccountNumberChecksum(accountNumber) { // Basic checksum validation - you might want to implement // a more specific algorithm based on bank requirements let sum = 0; for (let i = 0; i < accountNumber.length; i++) { sum += parseInt(accountNumber.charAt(i)); } // Simple check: sum shouldn't be too low (e.g., all zeros) return sum > 0; } function getAccountNumberErrorMessage(accountNumber) { if (!accountNumber) { return 'Account number is required'; } if (!/^\d+$/.test(accountNumber)) { return 'Account number must contain only numbers'; } if (accountNumber.length < 8) { return 'Account number must be at least 8 digits'; } if (accountNumber.length > 17) { return 'Account number cannot exceed 17 digits'; } if (isInvalidAccountNumberPattern(accountNumber)) { return 'Please enter a valid account number'; } return 'Please enter a valid account number'; } // Account Holder Name validation function validateAccountHolderName(input) { const errorElement = document.getElementById('account-holder-name-error'); const value = input.value; // Remove numbers and special characters (keep only letters and spaces) const cleanValue = value.replace(/[^a-zA-Z\s]/g, ''); if (value !== cleanValue) { input.value = cleanValue; } // Check content const hasOnlyNumbers = /^[0-9]+$/.test(value.replace(/\s/g, '')); const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(value.replace(/\s/g, '')); const hasLetters = /[a-zA-Z]/.test(value); if (value !== cleanValue && value.length > 0) { errorElement.textContent = 'Account holder name can only contain letters and spaces'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlyNumbers) { errorElement.textContent = 'Account holder name cannot contain only numbers'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlySpecialChars) { errorElement.textContent = 'Account holder name cannot contain only special characters'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (!hasLetters && value.trim().length > 0) { errorElement.textContent = 'Account holder name must include letters'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function preventInvalidAccountHolderChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122) || charCode === 32 || charCode === 8 || charCode === 9 || charCode === 37 || charCode === 39 || charCode === 46 || charCode === 16 || charCode === 17 || charCode === 18 || charCode === 91 || /^[a-zA-Z\s]$/.test(key)) { return true; } event.preventDefault(); return false; } function preventNonNumericZip(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; const currentValue = event.target.value; // Allow: numbers, hyphens (only in specific positions), and editing keys if ((charCode >= 48 && charCode <= 57) || // 0-9 (charCode === 45 && currentValue.length === 5 && !currentValue.includes('-')) || // hyphen only after 5 digits charCode === 8 || // backspace charCode === 9 || // tab charCode === 37 || // left arrow charCode === 39 || // right arrow charCode === 46 || // delete charCode === 16 || // shift charCode === 17 || // ctrl charCode === 18 || // alt charCode === 91) { // cmd (Mac) return true; } // Prevent all other characters including letters and special characters event.preventDefault(); return false; } // Function to validate ZIP code in real-time function validateZipCode(input) { let errorElement; // Determine which error element to use based on input ID if (input.id === 'billing-zip') { errorElement = document.getElementById('billing-zip-error'); } else if (input.id === 'card-zip') { errorElement = document.getElementById('card-zip-error'); } else { errorElement = document.getElementById('zip-error'); } const value = input.value; // Remove any non-numeric characters except hyphens const cleanValue = value.replace(/[^0-9\-]/g, ''); // If invalid characters were removed, update the input value if (value !== cleanValue) { input.value = cleanValue; } // Format ZIP code as user types (add hyphen after 5 digits) if (cleanValue.length === 5 && !cleanValue.includes('-')) { input.value = cleanValue; } else if (cleanValue.length > 5 && !cleanValue.includes('-')) { input.value = cleanValue.substring(0, 5) + '' + cleanValue.substring(5, 9); } if (input.value.length < 4) { errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } // Function to validate CVV in real-time function validateCVVVV(input) { const errorElement = document.getElementById('security-code-error'); const value = input.value; // Remove any non-digit characters const cleanValue = value.replace(/\D/g, ''); // If non-digits were removed, update the input value if (value !== cleanValue) { input.value = cleanValue; } // Get card type to determine expected CVV length const cardNumber = document.getElementById('card-number').value; const cardType = detectCardType(cardNumber); // Validate CVV format and content const isValid = validateCVVStrict(cleanValue, cardType); if (!isValid && cleanValue.length > 0) { errorElement.textContent = getCVVErrorMessage(cleanValue, cardType); errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } // Card type detection function detectCardType(cardNumber) { // Remove spaces and dashes const cleanNumber = cardNumber.replace(/[\s-]/g, ''); const cardPatterns = { visa: /^4[0-9]{12}(?:[0-9]{3})?$/, mastercard: /^5[1-5][0-9]{14}$/, amex: /^3[47][0-9]{13}$/, discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/, diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, jcb: /^(?:2131|1800|35\d{3})\d{11}$/ }; for (const [type, pattern] of Object.entries(cardPatterns)) { if (pattern.test(cleanNumber)) { return type; } } return 'unknown'; } // Function to prevent invalid character input in CVV field function preventInvalidCVVChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow only digits and common editing keys if ((charCode >= 48 && charCode <= 57) || // 0-9 charCode === 8 || // backspace charCode === 9 || // tab charCode === 37 || // left arrow charCode === 39 || // right arrow charCode === 46 || // delete charCode === 16 || // shift charCode === 17 || // ctrl charCode === 18 || // alt charCode === 91) { // cmd (Mac) return true; } // Prevent all non-digit characters event.preventDefault(); return false; } // Enhanced CVV validation function function validateCVVStrict(cvv, cardType) { if (!cvv || !/^\d+$/.test(cvv)) { return false; } // Check for invalid patterns if (isInvalidCVVPattern(cvv)) { return false; } const cvvLengths = { visa: 3, mastercard: 3, amex: 4, discover: 3, diners: 3, jcb: 3 }; const expectedLength = cvvLengths[cardType] || 3; // Check length if (cvv.length !== expectedLength) { return false; } return true; } // Function to check for invalid CVV patterns function isInvalidCVVPattern(cvv) { const invalidPatterns = [ /^0+$/, // All zeros: 000, 0000 /^1+$/, // All ones: 111, 1111 /^2+$/, // All twos: 222, 2222 /^3+$/, // All threes: 333, 3333 /^4+$/, // All fours: 444, 4444 /^5+$/, // All fives: 555, 5555 /^6+$/, // All sixes: 666, 6666 /^7+$/, // All sevens: 777, 7777 /^8+$/, // All eights: 888, 8888 /^9+$/, // All nines: 999, 9999 ]; return invalidPatterns.some(pattern => pattern.test(cvv)); } // Function to get appropriate CVV error message function getCVVErrorMessage(cvv, cardType) { if (!cvv) { return 'Security code is required'; } if (!/^\d+$/.test(cvv)) { return 'Security code must contain only numbers'; } if (isInvalidCVVPattern(cvv)) { return 'Please enter a valid security code'; } const cvvLengths = { visa: 3, mastercard: 3, amex: 4, discover: 3, diners: 3, jcb: 3 }; const expectedLength = cvvLengths[cardType] || 3; if (cvv.length !== expectedLength) { if (cardType === 'amex') { return 'American Express cards require a 4-digit security code'; } else { return `Security code must be ${expectedLength} digits`; } } return 'Please enter a valid security code'; } document.addEventListener('DOMContentLoaded', function() { // Real-time card number formatting and validation const cardNumberInput = document.getElementById('card-number'); if (cardNumberInput) { cardNumberInput.addEventListener('input', function(e) { const cursorPosition = e.target.selectionStart; const originalLength = e.target.value.length; // Format card number with spaces let value = e.target.value.replace(/\s/g, ''); let formattedValue = ''; for (let i = 0; i < value.length; i++) { if (i > 0 && i % 4 === 0) { formattedValue += ' '; } formattedValue += value[i]; } e.target.value = formattedValue; let newCursorPosition = cursorPosition; if (formattedValue.length > originalLength) { const addedChars = formattedValue.length - originalLength; // Count how many spaces were added before the cursor position let spacesAddedBeforeCursor = 0; for (let i = 0; i < cursorPosition && i < formattedValue.length; i++) { if (formattedValue[i] === ' ' && (i < cursorPosition || (i === cursorPosition && formattedValue[i] === ' '))) { spacesAddedBeforeCursor++; } } newCursorPosition = cursorPosition + spacesAddedBeforeCursor; } else if (formattedValue.length < originalLength) { const removedChars = originalLength - formattedValue.length; // Count how many spaces were removed before the cursor position let spacesRemovedBeforeCursor = 0; const oldValue = e.target.getAttribute('data-old-value') || ''; for (let i = 0; i < cursorPosition && i < oldValue.length; i++) { if (oldValue[i] === ' ' && i < cursorPosition) { spacesRemovedBeforeCursor++; } } newCursorPosition = Math.max(0, cursorPosition - spacesRemovedBeforeCursor); } e.target.setAttribute('data-old-value', formattedValue); e.target.setSelectionRange(newCursorPosition, newCursorPosition); // Real-time card type detection const cardType = detectCardType(value); if (cardType !== 'unknown') { // You can add visual feedback for card type here console.log('Card type:', cardType); } }); cardNumberInput.addEventListener('keydown', function(e) { // Allow: backspace, delete, tab, escape, enter, arrows, home, end if ([8, 9, 13, 27, 37, 38, 39, 40, 46].includes(e.keyCode) || // Allow: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X (e.keyCode === 65 && e.ctrlKey === true) || (e.keyCode === 67 && e.ctrlKey === true) || (e.keyCode === 86 && e.ctrlKey === true) || (e.keyCode === 88 && e.ctrlKey === true) || // Allow: numbers and numpad numbers (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105)) { return; } // Prevent all other keys e.preventDefault(); }); } // Real-time expiry date formatting const expiryInput = document.getElementById('expiry-date'); if (expiryInput) { expiryInput.addEventListener('input', function(e) { let value = e.target.value.replace(/\D/g, ''); if (value.length >= 2) { value = value.substring(0, 2) + '/' + value.substring(2, 4); } e.target.value = value; }); } // Real-time CVV validation const cvvInput = document.getElementById('security-code'); if (cvvInput) { cvvInput.addEventListener('input', function(e) { e.target.value = e.target.value.replace(/\D/g, '').substring(0, 4); }); } }); function showBankError(fieldId = null) { // Highlight specific field if provided if (fieldId) { const field = document.getElementById(fieldId); if (field) { field.classList.add('field-error'); // Remove error after 5 seconds setTimeout(() => { field.classList.remove('field-error'); }, 5000); } } }