Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
cf97a7a65b |
|
|
@ -1978,3 +1978,13 @@ tr[data-vaid]:hover {
|
|||
transform: none;
|
||||
color: var(--accent-light);
|
||||
}
|
||||
|
||||
|
||||
.field-error {
|
||||
border: 1px solid var(--error-color) !important;
|
||||
background-color: rgba(220, 38, 38, 0.05) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .field-error {
|
||||
background-color: rgba(220, 38, 38, 0.1) !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { ClientModel } from "../../../models/vaModel.js";
|
||||
import authFetch from "../../../utils/authFetch.js";
|
||||
|
||||
async function update_client_bank_details(req, res) {
|
||||
|
||||
try {
|
||||
const clientId = req.body.clientId;
|
||||
const clientEmail = req.body.clientEmail;
|
||||
const payload = req.body || {};
|
||||
const { userDetails } = payload;
|
||||
|
||||
const client = await ClientModel.findById(clientId);
|
||||
if (!client) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Client not found"
|
||||
});
|
||||
}
|
||||
|
||||
if (!userDetails) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Missing userDetails in request body"
|
||||
});
|
||||
}
|
||||
|
||||
userDetails._id = clientId; // Ensure the _id is set to clientId
|
||||
userDetails.email = clientEmail; // Ensure the email is set to clientEmail
|
||||
|
||||
// console.log("Final userDetails to update:", userDetails);
|
||||
|
||||
try {
|
||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}/services/apexrest/docva/api/client`;
|
||||
|
||||
const response = await authFetch(salesforceUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ userDetails })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
// console.log("✅ Successfully synced VA data to Salesforce:", result);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Client bank details updated and synced with Salesforce successfully",
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to sync with Salesforce:", error.message);
|
||||
console.log(error)
|
||||
|
||||
// Create a custom error to distinguish Salesforce errors
|
||||
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||
salesforceError.isSalesforceError = true;
|
||||
throw salesforceError;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.message === "Client not found") {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Internal Server Error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { update_client_bank_details };
|
||||
|
|
@ -15,6 +15,7 @@ import { client_dashboard_projects } from "../../controllers/client/dashboard/cl
|
|||
import { client_logout_callback } from "../../controllers/client/dashboard/clientLogout.controller.js";
|
||||
import { va_tips_delete, va_tips_get, va_tips_manage_function, va_tips_update } from "../../controllers/va/vaTips/vaTipsManage.controller.js";
|
||||
import { clientDashboardGetVaLogsController } from "../../controllers/client/dashboard/clientDashboardGetVaLogs.controller.js";
|
||||
import { update_client_bank_details } from "../../controllers/client/dashboard/clientUpdateBankDetails.controller.js";
|
||||
|
||||
// clients routes
|
||||
clientRoutes.post("/register", va_register_client); // Register a new VA
|
||||
|
|
@ -40,6 +41,7 @@ clientRoutes.post("/dashboard/va-report", clientAuthValidator, client_dashboard_
|
|||
clientRoutes.get("/dashboard-projects", clientAuthValidator, client_dashboard_projects); // VA Clients Projects Callback
|
||||
clientRoutes.get("/profile", clientAuthValidator, get_client_dashboard_profile);
|
||||
clientRoutes.patch("/profile", clientAuthValidator, update_client_password);
|
||||
clientRoutes.post("/profile", clientAuthValidator, update_client_bank_details);
|
||||
clientRoutes.patch("/:clientId", update_client);
|
||||
clientRoutes.delete("/:clientId", delete_client);
|
||||
|
||||
|
|
|
|||
|
|
@ -568,6 +568,369 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Change bank details
|
||||
async function changeBankDetails(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = {
|
||||
userDetails: {}
|
||||
};
|
||||
|
||||
const activePaymentDetails = document.querySelector('.payment-details.active');
|
||||
if (activePaymentDetails) {
|
||||
const requiredFields = activePaymentDetails.querySelectorAll('[required]');
|
||||
let isValid = true;
|
||||
formData.userDetails.paymentMethod = activePaymentDetails.id === 'card-details' ? 'card' : 'bank';
|
||||
|
||||
// Validate card payment
|
||||
if (formData.userDetails.paymentMethod === 'card') {
|
||||
// Card number validation
|
||||
const cardNumber = document.getElementById('card-number').value;
|
||||
if (!validateCardNumber(cardNumber)) {
|
||||
isValid = false;
|
||||
showError('card-number');
|
||||
return isValid;
|
||||
} else {
|
||||
// Show card type
|
||||
const cardType = detectCardType(cardNumber);
|
||||
if (cardType !== 'unknown') {
|
||||
// You can display card type to user if needed
|
||||
console.log('Detected card type:', cardType);
|
||||
}
|
||||
}
|
||||
|
||||
// Expiry date validation
|
||||
const expiryDate = document.getElementById('expiry-date').value;
|
||||
if (!validateExpiryDate(expiryDate)) {
|
||||
isValid = false;
|
||||
showError('expiry-date');
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// CVV validation
|
||||
const cvv = document.getElementById('security-code').value;
|
||||
const cardType = detectCardType(cardNumber);
|
||||
if (!validateCVV(cvv, cardType)) {
|
||||
isValid = false;
|
||||
showError('security-code');
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// ZIP code validation
|
||||
const zipCode = document.getElementById('card-zip').value;
|
||||
if (!validateZipCodeRegex(zipCode)) {
|
||||
isValid = false;
|
||||
showError('card-zip');
|
||||
return isValid;
|
||||
}
|
||||
|
||||
}
|
||||
// Validate bank payment
|
||||
else if (formData.userDetails.paymentMethod === 'bank') {
|
||||
// Account number validation
|
||||
const accountNumber = document.getElementById('account-number').value;
|
||||
if (!validateBankAccount(accountNumber)) {
|
||||
isValid = false;
|
||||
showError('account-number');
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Routing number validation
|
||||
const routingNumber = document.getElementById('routing-number').value;
|
||||
if (!validateRoutingNumber(routingNumber)) {
|
||||
isValid = false;
|
||||
showError('routing-number');
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Basic validation for other bank fields
|
||||
requiredFields.forEach(field => {
|
||||
if (!field.value.trim()) {
|
||||
isValid = false;
|
||||
field.classList.add('field-error');
|
||||
if (isValid === false) {
|
||||
showError(field.id);
|
||||
}
|
||||
} else {
|
||||
field.classList.remove('field-error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let accountCardNumber = document.getElementById('card-number');
|
||||
let accountExpiryDate = document.getElementById('expiry-date');
|
||||
let accountSecurityCode = document.getElementById('security-code');
|
||||
let accountCardZip = document.getElementById('card-zip');
|
||||
let accountBankName = document.getElementById('bank-name');
|
||||
let accountAccountNumber = document.getElementById('account-number');
|
||||
let accountRoutingNumber = document.getElementById('routing-number');
|
||||
let accountAccountType = document.getElementById('account-type');
|
||||
let accountAccountHolderName = document.getElementById('account-holder-name');
|
||||
|
||||
if(accountCardNumber.value) {
|
||||
formData.userDetails.cardNumber = accountCardNumber.value;
|
||||
}
|
||||
|
||||
if(accountExpiryDate.value) {
|
||||
formData.userDetails.expiryDate = accountExpiryDate.value;
|
||||
}
|
||||
|
||||
if(accountSecurityCode.value) {
|
||||
formData.userDetails.securityCode = accountSecurityCode.value;
|
||||
}
|
||||
|
||||
if(accountCardZip.value) {
|
||||
formData.userDetails.cardZip = accountCardZip.value;
|
||||
}
|
||||
|
||||
if(accountBankName.value) {
|
||||
formData.userDetails.bankName = accountBankName.value;
|
||||
}
|
||||
|
||||
if(accountAccountNumber.value) {
|
||||
formData.userDetails.accountNumber = accountAccountNumber.value;
|
||||
}
|
||||
|
||||
if(accountRoutingNumber.value) {
|
||||
formData.userDetails.routingNumber = accountRoutingNumber.value;
|
||||
}
|
||||
|
||||
if(accountAccountType.value) {
|
||||
formData.userDetails.accountType = accountAccountType.value;
|
||||
}
|
||||
|
||||
if(accountAccountHolderName.value) {
|
||||
formData.userDetails.accountHolderName = accountAccountHolderName.value;
|
||||
}
|
||||
|
||||
try {
|
||||
const saveBtn = document.getElementById('saveBankDetailsBtn');
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Updatind Details...';
|
||||
|
||||
const response = await fetchWithAuth(`${API_BASE}/profile`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Update Bank Details';
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('Bank details updated successfully!');
|
||||
closeBankDetailsModal();
|
||||
} else {
|
||||
alert(result.message || 'Failed to update bank details');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error changing Bank Details:', error);
|
||||
alert('Error changing bank details');
|
||||
} finally {
|
||||
const saveBtn = document.getElementById('saveBankDetailsBtn');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Update Bank Details';
|
||||
}
|
||||
}
|
||||
|
||||
// Bank account validation
|
||||
function validateBankAccount(accountNumber) {
|
||||
return /^\d{8,17}$/.test(accountNumber.replace(/\s/g, ''));
|
||||
}
|
||||
|
||||
// Routing number validation (basic US routing number check)
|
||||
function validateRoutingNumber(routingNumber) {
|
||||
const cleanRouting = routingNumber.replace(/\s/g, '');
|
||||
|
||||
if (!/^\d{9}$/.test(cleanRouting)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for invalid patterns
|
||||
if (isInvalidRoutingNumberPattern(cleanRouting)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ABA routing number checksum validation
|
||||
return isValidABAChecksum(cleanRouting);
|
||||
}
|
||||
|
||||
function isInvalidRoutingNumberPattern(routingNumber) {
|
||||
const invalidPatterns = [
|
||||
/^0+$/, // All zeros
|
||||
/^1+$/, // All ones
|
||||
/^2+$/, // All twos
|
||||
/^3+$/, // All threes
|
||||
/^4+$/, // All fours
|
||||
/^5+$/, // All fives
|
||||
/^6+$/, // All sixes
|
||||
/^7+$/, // All sevens
|
||||
/^8+$/, // All eights
|
||||
/^9+$/, // All nines
|
||||
/^012345678/, // Sequential
|
||||
/^876543210/, // Reverse sequential
|
||||
];
|
||||
|
||||
return invalidPatterns.some(pattern => pattern.test(routingNumber));
|
||||
}
|
||||
|
||||
function isValidABAChecksum(routingNumber) {
|
||||
const digits = routingNumber.split('').map(Number);
|
||||
|
||||
// ABA routing number checksum formula
|
||||
const sum = (3 * (digits[0] + digits[3] + digits[6])) +
|
||||
(7 * (digits[1] + digits[4] + digits[7])) +
|
||||
(1 * (digits[2] + digits[5] + digits[8]));
|
||||
|
||||
return sum % 10 === 0;
|
||||
}
|
||||
|
||||
|
||||
// Expiry date validation
|
||||
function validateExpiryDate(expiryDate) {
|
||||
const expiryRegex = /^(0[1-9]|1[0-2])\/([0-9]{2})$/;
|
||||
if (!expiryRegex.test(expiryDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [month, year] = expiryDate.split('/');
|
||||
const expiry = new Date(2000 + parseInt(year), parseInt(month));
|
||||
const currentDate = new Date();
|
||||
const currentMonth = new Date(currentDate.getFullYear(), currentDate.getMonth());
|
||||
|
||||
return expiry >= currentMonth;
|
||||
}
|
||||
|
||||
// CVV validation
|
||||
function validateCVV(cvv, cardType) {
|
||||
if (!/^\d+$/.test(cvv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cvvLengths = {
|
||||
visa: 3,
|
||||
mastercard: 3,
|
||||
amex: 4,
|
||||
discover: 3,
|
||||
diners: 3,
|
||||
jcb: 3
|
||||
};
|
||||
|
||||
const expectedLength = cvvLengths[cardType] || 3;
|
||||
return cvv.length === expectedLength;
|
||||
}
|
||||
|
||||
// ZIP code validation
|
||||
function validateZipCodeRegex(zipCode) {
|
||||
return /^\d{5}(-\d{4})?$/.test(zipCode);
|
||||
}
|
||||
|
||||
function validateCardNumber(cardNumber) {
|
||||
const cleanNumber = cardNumber.replace(/[\s-]/g, '');
|
||||
|
||||
// Check if it's all digits and has proper length
|
||||
if (!/^\d+$/.test(cleanNumber) || cleanNumber.length < 13 || cleanNumber.length > 19) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for invalid patterns
|
||||
if (isInvalidCardPattern(cleanNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for invalid BIN ranges
|
||||
if (hasInvalidBIN(cleanNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Luhn algorithm
|
||||
return isValidLuhn(cleanNumber);
|
||||
}
|
||||
|
||||
// Enhanced Luhn algorithm
|
||||
function isValidLuhn(cardNumber) {
|
||||
let sum = 0;
|
||||
let isEven = false;
|
||||
|
||||
for (let i = cardNumber.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(cardNumber.charAt(i));
|
||||
|
||||
if (isEven) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
isEven = !isEven;
|
||||
}
|
||||
|
||||
return sum % 10 === 0;
|
||||
}
|
||||
|
||||
// Function to check for invalid card patterns
|
||||
function isInvalidCardPattern(cardNumber) {
|
||||
const invalidPatterns = [
|
||||
/^0+$/, // All zeros
|
||||
/^1+$/, // All ones
|
||||
/^2+$/, // All twos
|
||||
/^3+$/, // All threes
|
||||
/^4+$/, // All fours
|
||||
/^5+$/, // All fives
|
||||
/^6+$/, // All sixes
|
||||
/^7+$/, // All sevens
|
||||
/^8+$/, // All eights
|
||||
/^9+$/, // All nines
|
||||
/^0123/, // Sequential
|
||||
/^1234/, // Sequential
|
||||
];
|
||||
|
||||
return invalidPatterns.some(pattern => pattern.test(cardNumber));
|
||||
}
|
||||
|
||||
// Function to check for invalid BIN ranges
|
||||
function hasInvalidBIN(cardNumber) {
|
||||
const firstSix = cardNumber.substring(0, 6);
|
||||
const firstFour = cardNumber.substring(0, 4);
|
||||
const firstTwo = cardNumber.substring(0, 2);
|
||||
|
||||
// Invalid BIN ranges
|
||||
const invalidBINs = [
|
||||
'000000', '000001', '000002', '000003', // Test cards should be handled separately
|
||||
'111111', '222222', '333333', '444444',
|
||||
'555555', '666666', '777777', '888888',
|
||||
'999999', '123456', '654321'
|
||||
];
|
||||
|
||||
// Check specific invalid ranges
|
||||
if (invalidBINs.includes(firstSix)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for known invalid starting digits
|
||||
if (['0', '3', '6', '7', '8'].includes(firstTwo)) {
|
||||
// 0 - Reserved
|
||||
// 3 - Should only be 34, 37 (AMEX)
|
||||
// 6 - Should follow Discover patterns
|
||||
// 7, 8 - Not currently assigned to major networks
|
||||
if (!isValidCardPrefix(cardNumber)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidCardPrefix(cardNumber) {
|
||||
const type = detectCardType(cardNumber);
|
||||
return type !== 'unknown';
|
||||
}
|
||||
|
||||
// Logout Modal Functions
|
||||
function openLogoutModal() {
|
||||
const modal = document.getElementById('logoutModal');
|
||||
|
|
@ -592,6 +955,7 @@
|
|||
|
||||
// Password form submission
|
||||
document.getElementById('passwordForm').addEventListener('submit', changePassword);
|
||||
document.getElementById('payment-form').addEventListener('submit', changeBankDetails);
|
||||
|
||||
// Close modal when clicking outside
|
||||
document.getElementById('passwordModal').addEventListener('click', function(e) {
|
||||
|
|
@ -1253,6 +1617,20 @@
|
|||
|
||||
});
|
||||
|
||||
function showError(fieldId = null) {
|
||||
// Highlight specific field if provided
|
||||
if (fieldId) {
|
||||
const field = document.getElementById(fieldId);
|
||||
if (field) {
|
||||
field.classList.add('field-error');
|
||||
// Remove error after 5 seconds
|
||||
setTimeout(() => {
|
||||
field.classList.remove('field-error');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue