// Email validation function function validateEmail(email) { const emailRegex = /^[a-zA-Z0-9](?:[a-zA-Z0-9]*[._-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9]*[-]?[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/; return emailRegex.test(email); } // 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'; } // Card number validation (Luhn algorithm) 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; } // Validate card prefix function isValidCardPrefix(cardNumber) { const type = detectCardType(cardNumber); return type !== 'unknown'; } // 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); } // 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; } const formData = { userDetails: {} }; // Current step tracking let currentStep = 1; const statesByCountry = { 'US': [ 'Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming' ], 'CA': [ 'Alberta', 'British Columbia', 'Manitoba', 'New Brunswick', 'Newfoundland and Labrador', 'Nova Scotia', 'Ontario', 'Prince Edward Island', 'Quebec', 'Saskatchewan' ], 'UK': [ 'England', 'Scotland', 'Wales', 'Northern Ireland' ], 'AU': [ 'New South Wales', 'Queensland', 'South Australia', 'Tasmania', 'Victoria', 'Western Australia', 'Australian Capital Territory', 'Northern Territory' ], 'IN': [ 'Andhra Pradesh', 'Arunachal Pradesh', 'Assam', 'Bihar', 'Chhattisgarh', 'Goa', 'Gujarat', 'Haryana', 'Himachal Pradesh', 'Jharkhand', 'Karnataka', 'Kerala', 'Madhya Pradesh', 'Maharashtra', 'Manipur', 'Meghalaya', 'Mizoram', 'Nagaland', 'Odisha', 'Punjab', 'Rajasthan', 'Sikkim', 'Tamil Nadu', 'Telangana', 'Tripura', 'Uttar Pradesh', 'Uttarakhand', 'West Bengal', 'Andaman and Nicobar Islands', 'Chandigarh', 'Dadra and Nagar Haveli and Daman and Diu', 'Delhi', 'Jammu and Kashmir', 'Ladakh', 'Lakshadweep', 'Puducherry' ], 'DE': [ 'Baden-Württemberg', 'Bavaria', 'Berlin', 'Brandenburg', 'Bremen', 'Hamburg', 'Hesse', 'Lower Saxony', 'Mecklenburg-Vorpommern', 'North Rhine-Westphalia', 'Rhineland-Palatinate', 'Saarland', 'Saxony', 'Saxony-Anhalt', 'Schleswig-Holstein', 'Thuringia' ], 'FR': [ 'Auvergne-Rhône-Alpes', 'Bourgogne-Franche-Comté', 'Brittany', 'Centre-Val de Loire', 'Corsica', 'Grand Est', 'Hauts-de-France', 'Île-de-France', 'Normandy', 'Nouvelle-Aquitaine', 'Occitanie', 'Pays de la Loire', 'Provence-Alpes-Côte d’Azur' ], 'IT': [ 'Abruzzo', 'Basilicata', 'Calabria', 'Campania', 'Emilia-Romagna', 'Friuli Venezia Giulia', 'Lazio', 'Liguria', 'Lombardy', 'Marche', 'Molise', 'Piedmont', 'Sardinia', 'Sicily', 'Trentino-Alto Adige', 'Tuscany', 'Umbria', 'Aosta Valley', 'Veneto' ], 'ES': [ 'Andalusia', 'Aragon', 'Asturias', 'Balearic Islands', 'Basque Country', 'Canary Islands', 'Cantabria', 'Castile and León', 'Castile-La Mancha', 'Catalonia', 'Extremadura', 'Galicia', 'La Rioja', 'Madrid', 'Murcia', 'Navarre', 'Valencia' ], 'BR': [ 'Acre', 'Alagoas', 'Amapá', 'Amazonas', 'Bahia', 'Ceará', 'Distrito Federal', 'Espírito Santo', 'Goiás', 'Maranhão', 'Mato Grosso', 'Mato Grosso do Sul', 'Minas Gerais', 'Pará', 'Paraíba', 'Paraná', 'Pernambuco', 'Piauí', 'Rio de Janeiro', 'Rio Grande do Norte', 'Rio Grande do Sul', 'Rondônia', 'Roraima', 'Santa Catarina', 'São Paulo', 'Sergipe', 'Tocantins' ], 'MX': [ 'Aguascalientes', 'Baja California', 'Baja California Sur', 'Campeche', 'Chiapas', 'Chihuahua', 'Coahuila', 'Colima', 'Durango', 'Guanajuato', 'Guerrero', 'Hidalgo', 'Jalisco', 'Mexico State', 'Michoacán', 'Morelos', 'Nayarit', 'Nuevo León', 'Oaxaca', 'Puebla', 'Querétaro', 'Quintana Roo', 'San Luis Potosí', 'Sinaloa', 'Sonora', 'Tabasco', 'Tamaulipas', 'Tlaxcala', 'Veracruz', 'Yucatán', 'Zacatecas' ], 'CN': [ 'Anhui', 'Beijing', 'Chongqing', 'Fujian', 'Gansu', 'Guangdong', 'Guangxi', 'Guizhou', 'Hainan', 'Hebei', 'Heilongjiang', 'Henan', 'Hubei', 'Hunan', 'Inner Mongolia', 'Jiangsu', 'Jiangxi', 'Jilin', 'Liaoning', 'Ningxia', 'Qinghai', 'Shaanxi', 'Shandong', 'Shanghai', 'Shanxi', 'Sichuan', 'Tianjin', 'Tibet', 'Xinjiang', 'Yunnan', 'Zhejiang' ], 'JP': [ 'Aichi', 'Akita', 'Aomori', 'Chiba', 'Ehime', 'Fukui', 'Fukuoka', 'Fukushima', 'Gifu', 'Gunma', 'Hiroshima', 'Hokkaido', 'Hyogo', 'Ibaraki', 'Ishikawa', 'Iwate', 'Kagawa', 'Kagoshima', 'Kanagawa', 'Kochi', 'Kumamoto', 'Kyoto', 'Mie', 'Miyagi', 'Miyazaki', 'Nagano', 'Nagasaki', 'Nara', 'Niigata', 'Oita', 'Okayama', 'Okinawa', 'Osaka', 'Saga', 'Saitama', 'Shiga', 'Shimane', 'Shizuoka', 'Tochigi', 'Tokushima', 'Tokyo', 'Tottori', 'Toyama', 'Wakayama', 'Yamagata', 'Yamaguchi', 'Yamanashi' ] }; // Function to navigate to any step (for tab navigation) function goToStep(step) { // Only allow navigation to completed steps or the next step if (step > currentStep && step > getMaxCompletedStep() + 1) { return; } // Hide current step document.getElementById(`step-${currentStep}`).classList.remove('active'); document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => { el.classList.remove('active'); }); // Show target step document.getElementById(`step-${step}`).classList.add('active'); document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => { el.classList.add('active'); }); currentStep = step; } // Function to get the maximum completed step function getMaxCompletedStep() { const completedSteps = document.querySelectorAll('.step.completed'); let maxStep = 0; completedSteps.forEach(step => { const stepNum = parseInt(step.getAttribute('data-step')); if (stepNum > maxStep) maxStep = stepNum; }); return maxStep; } async function handleNextStep(step) { await nextStep(step); } // Function to navigate to next step async function nextStep(step) { // Validate current step before proceeding if (!(await validateStep(currentStep))) { return; } // Save current step data saveStepData(currentStep); // Mark current step as completed document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => { el.classList.add('completed'); const stepNumber = el.querySelector('.step-number'); stepNumber.textContent = '✓'; stepNumber.style.backgroundColor = '#000000'; stepNumber.style.borderColor = '#000000'; stepNumber.style.color = 'white'; }); // Hide current step document.getElementById(`step-${currentStep}`).classList.remove('active'); document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => { el.classList.remove('active'); }); // Show next step document.getElementById(`step-${step}`).classList.add('active'); document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => { el.classList.add('active'); }); currentStep = step; } // Function to navigate to previous step function prevStep(step) { // Hide current step document.getElementById(`step-${currentStep}`).classList.remove('active'); document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => { el.classList.remove('active'); }); // Show previous step document.getElementById(`step-${step}`).classList.add('active'); document.querySelectorAll(`.step[data-step="${step}"]`).forEach(el => { el.classList.add('active'); }); currentStep = step; } // Function to validate current step async function validateStep(step) { let isValid = true; const form = document.getElementById(getFormId(step)); if (form) { if (step === 5) { const selectedPayment = document.querySelector('.payment-option.selected'); if (!selectedPayment) { isValid = false; showError('Please select a payment method', null, step); return isValid; } const activePaymentDetails = document.querySelector('.payment-details.active'); if (activePaymentDetails) { const requiredFields = activePaymentDetails.querySelectorAll('[required]'); // Validate card payment if (formData.userDetails.paymentMethod === 'card') { // Card number validation const cardNumber = document.getElementById('card-number').value; if (!validateCardNumber(cardNumber)) { isValid = false; showError('Please enter a valid card number', 'card-number', step); 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; showError('Please enter a valid expiry date (MM/YY)', 'expiry-date', step); return isValid; } // CVV validation const cvv = document.getElementById('security-code').value; const cardType = detectCardType(cardNumber); if (!validateCVV(cvv, cardType)) { isValid = false; showError(`Please enter a valid ${cardType === 'amex' ? '4-digit' : '3-digit'} CVV`, 'security-code', step); return isValid; } // ZIP code validation const zipCode = document.getElementById('card-zip').value; if (!validateZipCodeRegex(zipCode)) { isValid = false; showError('Please enter a valid ZIP code', 'card-zip', step); 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; showError('Please enter a valid account number (8-17 digits)', 'account-number', step); return isValid; } // Routing number validation const routingNumber = document.getElementById('routing-number').value; if (!validateRoutingNumber(routingNumber)) { isValid = false; showError('Please enter a valid routing numbers', 'routing-number', step); 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) { showError(`Please fill in ${field.labels[0]?.textContent || 'this field'}`, field.id, step); } } else { field.classList.remove('field-error'); } }); } } } else if (step === 1) { const email = document.getElementById('email').value; const firstName = document.getElementById('first-name').value; const lastName = document.getElementById('last-name').value; const password = document.getElementById('password').value; const confirmPassword = document.getElementById('confirmPassword').value; if (!firstName && !lastName && !password && !confirmPassword) { isValid = false; showError('Fields are Required', null, step); let requiredFields1 = form.querySelectorAll('[required]'); requiredFields1.forEach(field => { if (!field.value.trim()) { isValid = false; field.classList.add('field-error'); } else if (field.id !== 'email') { field.classList.remove('field-error'); } }); return isValid; } if (!validateEmail(email)) { isValid = false; showError('Please enter a valid email address', 'email', step); } else { const isEmailVerified = await verifyEmail(email); if (!isEmailVerified) { showError('Email already registered. Please use a different email address.', 'email', step); isValid = false; } } if (!firstName) { isValid = false; showError('First Name is Required', 'first-name', step); return isValid; } if (!lastName) { isValid = false; showError('Last Name is Required', 'last-name', step); return isValid; } if (password.length < 8) { showError('Password must be at least 8 characters long', 'password', step); isValid = false; } if (password !== confirmPassword) { document.getElementById('password-match-error').style.display = 'block'; document.getElementById('confirmPassword').classList.add('field-error'); showError('Passwords do not match', 'confirmPassword', step); isValid = false; } else { document.getElementById('password-match-error').style.display = 'none'; document.getElementById('confirmPassword').classList.remove('field-error'); } // Validate other required fields const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(field => { if (field.id !== 'email' && !field.value.trim()) { isValid = false; field.classList.add('field-error'); } else if (field.id !== 'email') { field.classList.remove('field-error'); } }); } else if (step === 2) { const companyName = document.getElementById('company-name').value; if (!companyName) { isValid = false; showError('Company Name is Required', 'company-name', step); return isValid; } // Validate other required fields const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(field => { if (field.id !== 'email' && !field.value.trim()) { isValid = false; field.classList.add('field-error'); } else if (field.id !== 'email') { field.classList.remove('field-error'); } }); } else if (step === 3) { const email = document.getElementById('billing-email').value; if (!validateEmail(email)) { isValid = false; showError('Please enter a valid email address', 'billing-email', step); } // Validate other required fields const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(field => { if (field.id !== 'email' && !field.value.trim()) { isValid = false; field.classList.add('field-error'); } else if (field.id !== 'email') { field.classList.remove('field-error'); } }); } else if (step === 4) { const email = document.getElementById('signatory-email').value; if (!validateEmail(email)) { isValid = false; showError('Please enter a valid email address', 'signatory-email', step); } const signatoryName = document.getElementById('signatory-name').value; if (!signatoryName) { isValid = false; showError('Please enter a valid name', 'signatory-name', step); } if (!signatoryName && !validateEmail(email)) { isValid = false; showError('Name and Email are Required', 'signatory-name', step); return isValid; } // Validate other required fields const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(field => { if (field.id !== 'email' && !field.value.trim()) { isValid = false; field.classList.add('field-error'); } else if (field.id !== 'email') { field.classList.remove('field-error'); } }); } else { // For other steps, validate all required fields normally const requiredFields = form.querySelectorAll('[required]'); requiredFields.forEach(field => { if (!field.value.trim()) { isValid = false; field.classList.add('field-error'); } else { field.classList.remove('field-error'); } }); } } return isValid; } // Function to get form ID based on step function getFormId(step) { switch(step) { case 1: return 'account-form'; case 2: return 'business-form'; case 3: return 'billing-form'; case 4: return 'signatory-form'; case 5: return 'payment-form'; default: return ''; } } // Function to save step data to formData object function saveStepData(step) { switch(step) { case 1: formData.userDetails.firstName = document.getElementById('first-name').value; formData.userDetails.lastName = document.getElementById('last-name').value; formData.userDetails.email = document.getElementById('email').value; formData.userDetails.password = document.getElementById('password').value; break; case 2: formData.userDetails.name = document.getElementById('company-name').value; formData.userDetails.address = document.getElementById('address').value; formData.userDetails.city = document.getElementById('city').value; formData.userDetails.state = document.getElementById('state').value; formData.userDetails.zip = document.getElementById('zip').value; break; case 3: formData.userDetails.billingName = document.getElementById('billing-name').value; formData.userDetails.billingAddress = document.getElementById('billing-address').value; formData.userDetails.billingCity = document.getElementById('billing-city').value; formData.userDetails.billingState = document.getElementById('billing-state').value; formData.userDetails.billingZip = document.getElementById('billing-zip').value; formData.userDetails.billingEmail = document.getElementById('billing-email').value; break; case 4: formData.userDetails.position = document.getElementById('position').value; formData.userDetails.signatoryName = document.getElementById('signatory-name').value; formData.userDetails.signatoryEmail = document.getElementById('signatory-email').value; break; case 5: // Payment method already handled in selectPaymentOption break; } } // 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'); formData.userDetails.paymentMethod = 'card'; // 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'); formData.userDetails.paymentMethod = 'bank'; // 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'); } } // Function to submit the form async function submitForm() { // Validate current step if (!(await validateStep(currentStep))) { return; } // Save current step data saveStepData(currentStep); // Save payment details based on selected method let cardNumber = document.getElementById('card-number'); let expiryDate = document.getElementById('expiry-date'); let securityCode = document.getElementById('security-code'); let cardZip = document.getElementById('card-zip'); let bankName = document.getElementById('bank-name'); let accountNumber = document.getElementById('account-number'); let routingNumber = document.getElementById('routing-number'); let accountType = document.getElementById('account-type'); let accountHolderName = document.getElementById('account-holder-name'); if(cardNumber.value) { formData.userDetails.cardNumber = cardNumber.value; } if(expiryDate.value) { formData.userDetails.expiryDate = expiryDate.value; } if(securityCode.value) { formData.userDetails.securityCode = securityCode.value; } if(cardZip.value) { formData.userDetails.cardZip = cardZip.value; } if(bankName.value) { formData.userDetails.bankName = bankName.value; } if(accountNumber.value) { formData.userDetails.accountNumber = accountNumber.value; } if(routingNumber.value) { formData.userDetails.routingNumber = routingNumber.value; } if(accountType.value) { formData.userDetails.accountType = accountType.value; } if(accountHolderName.value) { formData.userDetails.accountHolderName = accountHolderName.value; } // Show loading state const submitBtn = document.querySelector('#payment-form .btn-primary'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Submitting...'; submitBtn.disabled = true; try { // console.log('Submitting form data:', formData); // Send data to server using fetch const response = await fetch('/api/v1/client/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); if (response.ok) { // Success - move to success step // Mark current step as completed document.querySelectorAll(`.step[data-step="${currentStep}"]`).forEach(el => { el.classList.add('completed'); const stepNumber = el.querySelector('.step-number'); stepNumber.textContent = '✓'; stepNumber.style.backgroundColor = '#000000'; stepNumber.style.borderColor = '#000000'; stepNumber.style.color = 'white'; }); nextStep(6); setTimeout(() => { window.location.href = '/'; }, 1000); } else { // Handle server errors if (response.status === 409) { showError('A user with this email already exists. Please use a different email.', null, 5); } else { showError('There was an error submitting your registration. Please try again.', null, 5); } } } catch (error) { // Handle network errors console.error('Error:', error); showError('There was a network error. Please check your connection and try again.', null, 5); } finally { // Restore button state submitBtn.textContent = originalText; submitBtn.disabled = false; } } // Function to fetch states from API async function fetchStates(countryCode) { try { // Replace with your actual API endpoint const response = await fetch(`/api/states/${countryCode}`); if (response.ok) { const states = await response.json(); return states; } else { console.error('Failed to fetch states'); return []; } } catch (error) { console.error('Error fetching states:', error); return []; } } // Function to populate states dropdown async function populateStates(countryCode, selectElementId) { const selectElement = document.getElementById(selectElementId); const states = statesByCountry[countryCode] || []; // Clear existing options except the first empty one while (selectElement.options.length > 1) { selectElement.remove(1); } // Add states as options states.forEach(state => { const option = document.createElement('option'); option.value = state; option.textContent = state; selectElement.appendChild(option); }); } // Function to detect user location async function detectLocation() { // Show loading indicators const stateLoader = document.getElementById('state-loading'); if (stateLoader) stateLoader.style.display = 'block'; try { const response = await fetch('https://ipapi.co/json/'); const data = await response.json(); // Hide loading indicators if (stateLoader) stateLoader.style.display = 'none'; // const countryCode = data.country_code || 'US'; const supportedCountries = Object.keys(statesByCountry); const countryCode = supportedCountries.includes(data.country_code) ? data.country_code : 'US'; const region = data.region || ''; // const city = data.city || ''; // const zip = data.postal || ''; // Populate city and state based on detected location // const cityInput = document.getElementById('city'); // const zipInput = document.getElementById('zip'); // if (cityInput) cityInput.value = city; // if (zipInput) zipInput.value = zip; // Populate states dropdown for the detected country await populateStates(countryCode, 'state'); await populateStates(countryCode, 'billing-state'); // auto populate state if region matches // const stateSelect = document.getElementById('state'); // const billingStateSelect = document.getElementById('billing-state'); // if (stateSelect && region) { // const matchedState = Array.from(stateSelect.options).find( // opt => opt.text.toLowerCase() === region.toLowerCase() // ); // if (matchedState) { // stateSelect.value = matchedState.value; // stateSelect.dispatchEvent(new Event('change')); // } // } // if (billingStateSelect && region) { // const matchedBillingState = Array.from(billingStateSelect.options).find( // opt => opt.text.toLowerCase() === region.toLowerCase() // ); // if (matchedBillingState) { // billingStateSelect.value = matchedBillingState.value; // billingStateSelect.dispatchEvent(new Event('change')); // } // } } catch (error) { console.error('Error detecting location:', error); // Hide loading indicators if (stateLoader) stateLoader.style.display = 'none'; // Fallback to US states if detection fails await populateStates('US', 'state'); await populateStates('US', 'billing-state'); } } // Initialize form interactions 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); }); } // Real-time email validation const emailInputs = document.querySelectorAll('input[type="email"]'); emailInputs.forEach(emailInput => { if (emailInput) { emailInput.addEventListener('blur', function(e) { if (e.target.value && !validateEmail(e.target.value)) { e.target.style.border = '1px solid #ef4444'; } else { e.target.style.border = ''; } }); } }); // Add input event listeners to handle label animation document.querySelectorAll('.input-field').forEach(input => { input.addEventListener('input', function() { if (this.value) { this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.fontSize = '12px'; this.nextElementSibling.style.transform = 'none'; } else { this.nextElementSibling.style.top = '50%'; this.nextElementSibling.style.fontSize = '14px'; this.nextElementSibling.style.transform = 'translateY(-50%)'; } }); input.addEventListener('focus', function() { this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.fontSize = '12px'; this.nextElementSibling.style.transform = 'none'; }); input.addEventListener('blur', function() { if (!this.value) { this.nextElementSibling.style.top = '50%'; this.nextElementSibling.style.fontSize = '14px'; this.nextElementSibling.style.transform = 'translateY(-50%)'; } }); }); // Handle select field label animation document.querySelectorAll('.select-field').forEach(select => { select.addEventListener('change', function() { if (this.value) { this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.fontSize = '12px'; this.nextElementSibling.style.transform = 'none'; } else { this.nextElementSibling.style.top = '50%'; this.nextElementSibling.style.fontSize = '14px'; this.nextElementSibling.style.transform = 'translateY(-50%)'; } }); select.addEventListener('focus', function() { this.nextElementSibling.style.top = '4px'; this.nextElementSibling.style.fontSize = '12px'; this.nextElementSibling.style.transform = 'none'; }); select.addEventListener('blur', function() { if (!this.value) { this.nextElementSibling.style.top = '50%'; this.nextElementSibling.style.fontSize = '14px'; this.nextElementSibling.style.transform = 'translateY(-50%)'; } }); }); // Handle use business address checkbox document.getElementById('use-business-address').addEventListener('change', function() { if (this.checked) { document.getElementById('billing-name').value = formData.userDetails.name || ''; document.getElementById('billing-address').value = formData.userDetails.address || ''; document.getElementById('billing-city').value = formData.userDetails.city || ''; document.getElementById('billing-state').value = formData.userDetails.state || ''; document.getElementById('billing-zip').value = formData.userDetails.zip || ''; // Trigger input events to update labels document.querySelectorAll('#billing-form .input-field').forEach(input => { input.dispatchEvent(new Event('input')); }); // Trigger change event for select document.getElementById('billing-state').dispatchEvent(new Event('change')); } else { document.getElementById('billing-name').value = ''; document.getElementById('billing-address').value = ''; document.getElementById('billing-city').value = ''; document.getElementById('billing-state').value = ''; document.getElementById('billing-zip').value = ''; // Trigger input events to update labels document.querySelectorAll('#billing-form .input-field').forEach(input => { input.dispatchEvent(new Event('input')); }); // Trigger change event for select document.getElementById('billing-state').dispatchEvent(new Event('change')); } }); // Handle use account email checkbox document.getElementById('use-account-email').addEventListener('change', function() { if (this.checked) { document.getElementById('billing-email').value = formData.userDetails.email || ''; document.getElementById('billing-email').dispatchEvent(new Event('input')); } else { document.getElementById('billing-email').value = ''; document.getElementById('billing-email').dispatchEvent(new Event('input')); } }); // Detect user location when the page loads detectLocation(); }); async function verifyEmail(email) { try { const response = await fetch('/api/v1/client/verify-client-email', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({"email": email}) }); if (response.ok) { return false; } else { return true; } } catch (error) { // Handle network errors console.error('Error:', error); showError('There was a network error. Please check your connection and try again.', null, 1); return false; } } function showError(message, fieldId = null, step = null) { // Determine the current step if not provided const currentStep = step || getCurrentStep(); const errorContainer = document.getElementById(`step-${currentStep}-error`); const errorMessage = document.getElementById(`step-${currentStep}-error-message`); if (errorContainer && errorMessage) { errorMessage.textContent = message; errorContainer.style.display = 'block'; // Scroll to error container errorContainer.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 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); } } // Auto-hide error after 8 seconds setTimeout(() => { hideError(currentStep); }, 8000); } } function hideError(step = null) { if (step) { // Hide specific step error const errorContainer = document.getElementById(`step-${step}-error`); if (errorContainer) { errorContainer.style.display = 'none'; } } else { // Hide all step errors for (let i = 1; i <= 5; i++) { const errorContainer = document.getElementById(`step-${i}-error`); if (errorContainer) { errorContainer.style.display = 'none'; } } } } function clearFieldErrors() { document.querySelectorAll('.field-error').forEach(field => { field.classList.remove('field-error'); }); } function getCurrentStep() { return currentStep; } // Function to validate signatory name (no numbers allowed) function validateSignatoryName(input) { const errorElement = document.getElementById('signatory-name-error'); const value = input.value; // Remove any numbers from the input const cleanValue = value.replace(/[0-9]/g, ''); // If numbers were removed, update the input value if (value !== cleanValue) { input.value = cleanValue; } // Show error if original value contained numbers if (value !== cleanValue && value.length > 0) { 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 prevent number input in real-time function preventNumbers(event) { const charCode = event.which ? event.which : event.keyCode; // Allow letters, spaces, hyphens, apostrophes, and backspace if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe charCode === 8 || // backspace charCode === 46) { // delete return true; } // Prevent numbers and other characters event.preventDefault(); return false; } // Function to validate first name function validateFirstName(input) { const errorElement = document.getElementById('first-name-error'); const value = input.value; // Check various invalid patterns const validationResult = validateFirstNameContent(value); if (!validationResult.isValid && value.trim().length > 0) { errorElement.textContent = validationResult.message; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function validateFirstNameContent(firstName) { const trimmed = firstName.trim(); if (trimmed.length === 0) { return { isValid: false, message: 'First name is required' }; } // Check minimum length if (trimmed.length < 2) { return { isValid: false, message: 'First name must be at least 2 characters' }; } // Remove whitespace for content analysis const content = trimmed.replace(/\s/g, ''); // Check if contains only special characters if (/^[^a-zA-Z0-9]+$/.test(content)) { return { isValid: false, message: 'First name cannot be only special characters' }; } // Check if contains at least some valid content (letters or numbers) if (!/[a-zA-Z0-9]/.test(content)) { return { isValid: false, message: 'First name must contain letters or numbers' }; } // Optional: Check for excessive special characters (more than 50%) const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length; if (specialCharCount > content.length / 2) { return { isValid: false, message: 'First name contains too many special characters' }; } return { isValid: true, message: '' }; } // Function to prevent invalid character input in real-time function preventInvalidFirstNameChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas // Also allow common editing keys if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z (charCode >= 48 && charCode <= 57) || // 0-9 charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe charCode === 38 || // ampersand (&) charCode === 46 || // period (.) charCode === 44 || // comma (,) charCode === 8 || // backspace charCode === 9 || // tab charCode === 37 || // left arrow charCode === 39 || // right arrow charCode === 16 || // shift charCode === 17 || // ctrl charCode === 18 || // alt charCode === 91 || // cmd (Mac) /^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters return true; } // Prevent invalid characters event.preventDefault(); return false; } // Function to validate last name function validateLastName(input) { const errorElement = document.getElementById('last-name-error'); const value = input.value; // Check various invalid patterns const validationResult = validateLastNameContent(value); if (!validationResult.isValid && value.trim().length > 0) { errorElement.textContent = validationResult.message; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function validateLastNameContent(lastName) { const trimmed = lastName.trim(); if (trimmed.length === 0) { return { isValid: false, message: 'Last name is required' }; } // Check minimum length if (trimmed.length < 2) { return { isValid: false, message: 'Last name must be at least 2 characters' }; } // Remove whitespace for content analysis const content = trimmed.replace(/\s/g, ''); // Check if contains only special characters if (/^[^a-zA-Z0-9]+$/.test(content)) { return { isValid: false, message: 'Last name cannot be only special characters' }; } // Check if contains at least some valid content (letters or numbers) if (!/[a-zA-Z0-9]/.test(content)) { return { isValid: false, message: 'Last name must contain letters or numbers' }; } // Optional: Check for excessive special characters (more than 50%) const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length; if (specialCharCount > content.length / 2) { return { isValid: false, message: 'Last name contains too many special characters' }; } return { isValid: true, message: '' }; } // Function to prevent invalid character input in real-time function preventInvalidLastNameChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas // Also allow common editing keys if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z (charCode >= 48 && charCode <= 57) || // 0-9 charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe charCode === 38 || // ampersand (&) charCode === 46 || // period (.) charCode === 44 || // comma (,) charCode === 8 || // backspace charCode === 9 || // tab charCode === 37 || // left arrow charCode === 39 || // right arrow charCode === 16 || // shift charCode === 17 || // ctrl charCode === 18 || // alt charCode === 91 || // cmd (Mac) /^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters return true; } // Prevent invalid characters event.preventDefault(); return false; } // Function to validate company name function validateCompanyName(input) { const errorElement = document.getElementById('company-name-error'); const value = input.value; // Check various invalid patterns const validationResult = validateCompanyNameContent(value); if (!validationResult.isValid && value.trim().length > 0) { errorElement.textContent = validationResult.message; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else { errorElement.style.display = 'none'; input.classList.remove('field-error'); return true; } } function validateCompanyNameContent(companyName) { const trimmed = companyName.trim(); if (trimmed.length === 0) { return { isValid: false, message: 'Company name is required' }; } // Check minimum length if (trimmed.length < 2) { return { isValid: false, message: 'Company name must be at least 2 characters' }; } // Remove whitespace for content analysis const content = trimmed.replace(/\s/g, ''); // Check if contains only special characters if (/^[^a-zA-Z0-9]+$/.test(content)) { return { isValid: false, message: 'Company name cannot be only special characters' }; } // Check if contains at least some valid content (letters or numbers) if (!/[a-zA-Z0-9]/.test(content)) { return { isValid: false, message: 'Company name must contain letters or numbers' }; } // Optional: Check for excessive special characters (more than 50%) const specialCharCount = (content.match(/[^a-zA-Z0-9]/g) || []).length; if (specialCharCount > content.length / 2) { return { isValid: false, message: 'Company name contains too many special characters' }; } return { isValid: true, message: '' }; } // Function to prevent invalid character input in real-time function preventInvalidCompanyNameChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow: letters, numbers, spaces, hyphens, apostrophes, ampersands, periods, commas // Also allow common editing keys if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z (charCode >= 48 && charCode <= 57) || // 0-9 charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe charCode === 38 || // ampersand (&) charCode === 46 || // period (.) charCode === 44 || // comma (,) charCode === 8 || // backspace charCode === 9 || // tab charCode === 37 || // left arrow charCode === 39 || // right arrow charCode === 16 || // shift charCode === 17 || // ctrl charCode === 18 || // alt charCode === 91 || // cmd (Mac) /^[a-zA-Z0-9\s\-'&.,]$/.test(key)) { // allowed characters return true; } // Prevent invalid 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 prevent non-numeric input in ZIP fields 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 city name in real-time function validateCity(input) { let errorElement; // Determine which error element to use based on input ID if (input.id === 'billing-city') { errorElement = document.getElementById('billing-city-error'); } else { errorElement = document.getElementById('city-error'); } const value = input.value; // Remove invalid special characters (keep only letters, spaces, hyphens, apostrophes) const cleanValue = value.replace(/[^a-zA-Z\s\-']/g, ''); // If invalid characters were removed, update the input value if (value !== cleanValue) { input.value = cleanValue; } // Check if the value contains only numbers const numbersOnlyCheck = value.replace(/[\s\-']/g, ''); const hasOnlyNumbers = /^[0-9]+$/.test(numbersOnlyCheck) && numbersOnlyCheck.length > 0; // Check if the value contains only special characters const specialCharsOnlyCheck = value.replace(/[\s]/g, ''); const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(specialCharsOnlyCheck) && specialCharsOnlyCheck.length > 0; // Check if we have at least one letter const hasLetters = /[a-zA-Z]/.test(value); // Show appropriate error messages if (value !== cleanValue && value.length > 0) { errorElement.textContent = 'City name can only contain letters, spaces, hyphens, and apostrophes'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlyNumbers) { errorElement.textContent = 'City name cannot be only numbers. Please include letters.'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlySpecialChars) { errorElement.textContent = 'City name cannot be only special characters. Please include letters.'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (!hasLetters && value.trim().length > 0) { errorElement.textContent = 'City name must include at least one letter'; 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 prevent invalid character input in city fields function preventInvalidCityChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow: letters, spaces, hyphens, apostrophes, and editing keys if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe 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) /^[a-zA-Z\s\-']$/.test(key)) { // allowed characters return true; } // Prevent numbers and other special characters event.preventDefault(); return false; } function validatePosition(input) { const errorElement = document.getElementById('position-error'); const value = input.value; // Remove numbers and invalid special characters // Allow: letters, spaces, hyphens, apostrophes, periods, commas, ampersands, slashes const cleanValue = value.replace(/[^a-zA-Z\s\-'.,&\/]/g, ''); // If invalid characters were removed, update the input value if (value !== cleanValue) { input.value = cleanValue; } // Check if the value contains only numbers const numbersOnlyCheck = value.replace(/[\s\-'.,&\/]/g, ''); const hasOnlyNumbers = /^[0-9]+$/.test(numbersOnlyCheck) && numbersOnlyCheck.length > 0; // Check if the value contains only special characters const specialCharsOnlyCheck = value.replace(/[\s]/g, ''); const hasOnlySpecialChars = /^[^a-zA-Z0-9]+$/.test(specialCharsOnlyCheck) && specialCharsOnlyCheck.length > 0; // Check if we have at least one letter const hasLetters = /[a-zA-Z]/.test(value); // Show appropriate error messages if (value !== cleanValue && value.length > 0) { errorElement.textContent = 'Position can only contain letters, spaces, hyphens, apostrophes, periods, commas, ampersands, and slashes'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlyNumbers) { errorElement.textContent = 'Position cannot be only numbers. Please include letters.'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (hasOnlySpecialChars) { errorElement.textContent = 'Position cannot be only special characters. Please include letters.'; errorElement.style.display = 'block'; input.classList.add('field-error'); return false; } else if (!hasLetters && value.trim().length > 0) { errorElement.textContent = 'Position must include at least one letter'; 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 prevent invalid character input in position field function preventInvalidPositionChars(event) { const charCode = event.which ? event.which : event.keyCode; const key = event.key; // Allow: letters, spaces, hyphens, apostrophes, periods, commas, ampersands, slashes // Also allow common editing keys if ((charCode >= 65 && charCode <= 90) || // A-Z (charCode >= 97 && charCode <= 122) || // a-z charCode === 32 || // space charCode === 45 || // hyphen charCode === 39 || // apostrophe charCode === 46 || // period charCode === 44 || // comma charCode === 38 || // ampersand (&) charCode === 47 || // slash (/) 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) /^[a-zA-Z\s\-'.,&\/]$/.test(key)) { // allowed characters return true; } // Prevent numbers and other special characters event.preventDefault(); return false; } // 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; } } // 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'; } // 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 to generate a strong password function generatePassword() { const length = 12; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"; let password = ""; // Ensure at least one of each required character type password += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(Math.random() * 26)]; password += "abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 26)]; password += "0123456789"[Math.floor(Math.random() * 10)]; password += "!@#$%^&*"[Math.floor(Math.random() * 8)]; // Fill the rest randomly for (let i = password.length; i < length; i++) { password += charset.charAt(Math.floor(Math.random() * charset.length)); } // Shuffle the password password = password.split('').sort(() => 0.5 - Math.random()).join(''); // Set the password field document.getElementById('password').value = password; document.getElementById('confirmPassword').value = password; // Trigger input events to update UI document.getElementById('password').dispatchEvent(new Event('input')); document.getElementById('confirmPassword').dispatchEvent(new Event('input')); } // Function to toggle password visibility function togglePasswordVisibility() { const passwordField = document.getElementById('password'); const confirmField = document.getElementById('confirmPassword'); const toggleButton = document.querySelector('.password-toggle'); if (passwordField.type === 'password') { passwordField.type = 'text'; confirmField.type = 'text'; toggleButton.textContent = 'Hide Password'; } else { passwordField.type = 'password'; confirmField.type = 'password'; toggleButton.textContent = 'Show Password'; } }