Refactor invoice PDF generation and VAT validation
- Updated PDF template to display a fixed software code instead of "SOFTWARE". - Changed VAT label to include tax label dynamically and set to 0% for certain conditions. - Enhanced JavaScript for VAT number validation with asynchronous checks against the VIES database. - Implemented debounce for VAT number input to optimize validation calls. - Updated country settings to include country codes for VAT validation. - Modified email sending functions in webhook handlers to use dynamic attachment names for invoices.
This commit is contained in:
@@ -1482,6 +1482,48 @@ function showPaymentModal(option) {
|
||||
modal.appendChild(modalContent);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// VAT number validation state
|
||||
let vatValidationInProgress = false;
|
||||
let vatValidationResult = null;
|
||||
|
||||
// Function to check VAT number against VIES database (via server-side proxy)
|
||||
async function checkVATNumber(countryCode, vatNumber) {
|
||||
if (!countryCode || !vatNumber) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use server-side proxy to avoid CORS issues
|
||||
const serviceToken = document.getElementById("servicetoken")?.innerHTML || '';
|
||||
const url = link + '/v2/vat_check';
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': 'Bearer ' + serviceToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
countryCode: countryCode,
|
||||
vatNumber: vatNumber
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('VAT check HTTP error:', response.status);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('VIES response:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error checking VAT:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to calculate and update tax
|
||||
function updateTaxDisplay() {
|
||||
const selectedCountry = document.getElementById("paymentCountry").value;
|
||||
@@ -1500,12 +1542,12 @@ function showPaymentModal(option) {
|
||||
// Netherlands: always take the tax percentage
|
||||
taxRate = countryTaxRate;
|
||||
} else if (isEU) {
|
||||
if (vatNumber) {
|
||||
// EU with VAT number: 0% VAT, reverse charge
|
||||
if (vatNumber && vatValidationResult && vatValidationResult.valid === true) {
|
||||
// EU with VALID VAT number: 0% VAT, reverse charge
|
||||
taxRate = 0;
|
||||
vatNote = 'Reverse charge VAT';
|
||||
} else {
|
||||
// EU without VAT number: use country VAT
|
||||
// EU without VAT number or invalid VAT: use country VAT
|
||||
taxRate = countryTaxRate;
|
||||
vatNote = 'Local VAT';
|
||||
}
|
||||
@@ -1564,9 +1606,112 @@ function showPaymentModal(option) {
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce timer for VAT validation
|
||||
let vatValidationTimeout = null;
|
||||
|
||||
// Function to validate VAT number with visual feedback
|
||||
async function validateVATNumber() {
|
||||
const selectedCountry = document.getElementById("paymentCountry").value;
|
||||
const vatNumber = document.getElementById("paymentVatNumber").value.trim();
|
||||
const vatInput = document.getElementById("paymentVatNumber");
|
||||
|
||||
// Reset validation state
|
||||
vatValidationResult = null;
|
||||
vatInput.style.borderColor = '';
|
||||
|
||||
// Remove any existing validation message
|
||||
const existingMessage = document.getElementById('vatValidationMessage');
|
||||
if (existingMessage) {
|
||||
existingMessage.remove();
|
||||
}
|
||||
|
||||
if (!vatNumber) {
|
||||
updateTaxDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedCountry || typeof COUNTRIES === 'undefined' || !COUNTRIES) {
|
||||
updateTaxDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
|
||||
if (!countryData || countryData.eu !== 1 || !countryData.country_code) {
|
||||
updateTaxDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// For Netherlands, don't validate VAT (always apply VAT)
|
||||
if (selectedCountry === 'Netherlands') {
|
||||
updateTaxDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show validating state
|
||||
vatInput.style.borderColor = '#ffc107';
|
||||
vatValidationInProgress = true;
|
||||
|
||||
const validationMsg = document.createElement('div');
|
||||
validationMsg.id = 'vatValidationMessage';
|
||||
validationMsg.style.cssText = 'margin-top: 5px; font-size: 12px; color: #ffc107;';
|
||||
validationMsg.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Validating VAT number...';
|
||||
vatInput.parentNode.appendChild(validationMsg);
|
||||
|
||||
// Call VIES API
|
||||
const result = await checkVATNumber(countryData.country_code, vatNumber);
|
||||
vatValidationInProgress = false;
|
||||
|
||||
if (result && result.valid === true) {
|
||||
// VAT number is valid
|
||||
vatValidationResult = result;
|
||||
vatInput.style.borderColor = '#28a745';
|
||||
validationMsg.style.color = '#28a745';
|
||||
validationMsg.innerHTML = '<i class="fa-solid fa-check-circle"></i> Valid VAT number';
|
||||
|
||||
// Format VAT number as CountryCode + VatNumber (e.g., DE115235681)
|
||||
const formattedVAT = result.countryCode + result.vatNumber;
|
||||
if (vatInput.value !== formattedVAT) {
|
||||
vatInput.value = formattedVAT;
|
||||
}
|
||||
} else {
|
||||
// VAT number is invalid or check failed
|
||||
vatValidationResult = null;
|
||||
vatInput.style.borderColor = '#dc3545';
|
||||
validationMsg.style.color = '#dc3545';
|
||||
validationMsg.innerHTML = '<i class="fa-solid fa-times-circle"></i> Invalid VAT number or validation failed';
|
||||
}
|
||||
|
||||
// Update tax display with new validation result
|
||||
updateTaxDisplay();
|
||||
}
|
||||
|
||||
// Add event listeners to country select and VAT number to update tax
|
||||
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
|
||||
document.getElementById("paymentVatNumber").addEventListener('input', updateTaxDisplay);
|
||||
document.getElementById("paymentCountry").addEventListener('change', () => {
|
||||
vatValidationResult = null;
|
||||
const vatInput = document.getElementById("paymentVatNumber");
|
||||
vatInput.style.borderColor = '';
|
||||
const existingMessage = document.getElementById('vatValidationMessage');
|
||||
if (existingMessage) {
|
||||
existingMessage.remove();
|
||||
}
|
||||
updateTaxDisplay();
|
||||
|
||||
// Validate VAT if already entered
|
||||
if (vatInput.value.trim()) {
|
||||
if (vatValidationTimeout) {
|
||||
clearTimeout(vatValidationTimeout);
|
||||
}
|
||||
vatValidationTimeout = setTimeout(validateVATNumber, 500);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("paymentVatNumber").addEventListener('input', () => {
|
||||
// Debounce VAT validation
|
||||
if (vatValidationTimeout) {
|
||||
clearTimeout(vatValidationTimeout);
|
||||
}
|
||||
vatValidationTimeout = setTimeout(validateVATNumber, 1000);
|
||||
});
|
||||
|
||||
// Close modal on cancel
|
||||
document.getElementById("cancelPayment").onclick = () => {
|
||||
|
||||
Reference in New Issue
Block a user