1957 lines
70 KiB
JavaScript
1957 lines
70 KiB
JavaScript
// 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 companyName = document.getElementById('company-name').value;
|
||
if (!validateEmail(email) && !companyName) {
|
||
isValid = false;
|
||
showError('Both 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 (!companyName) {
|
||
isValid = false;
|
||
showError('Company / Client Name is Required', null, 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.name = document.getElementById('company-name').value;
|
||
formData.userDetails.email = document.getElementById('email').value;
|
||
break;
|
||
case 2:
|
||
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 = '/client/login';
|
||
}, 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');
|
||
|
||
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 company/client 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 preventInvalidCompanyChars(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;
|
||
} |