Refactor authorization and token refresh logic; update tax handling and invoice generation
- Changed variable name from `$stmt_service` to `$stmt_refreshkey` for clarity in `authorization.php` and `token_refresh.php`. - Added null coalescing operator to ensure criteria are set to an empty string if not provided in `products_software_versions.php`. - Modified SQL script to add `eu` column to `taxes` table and update tax rates based on EU membership. - Enhanced invoice generation logic in `functions.php` to include VAT notes based on customer country and VAT number. - Updated email and PDF templates to display VAT notes and percentages correctly. - Adjusted JavaScript tax calculation logic to handle VAT based on country and VAT number. - Fixed API URL in `index.php` for token refresh endpoint. - Updated countries data structure in `countries.php` to include EU membership status.
This commit is contained in:
@@ -4,6 +4,8 @@ TRUNCATE TABLE `role_access_permissions`;
|
||||
TRUNCATE TABLE `user_roles`;
|
||||
TRUNCATE TABLE `access_elements`;
|
||||
|
||||
ALTER TABLE taxes ADD COLUMN eu TINYINT(1) DEFAULT 0;
|
||||
|
||||
INSERT INTO access_elements (access_name,access_path,description,is_active,created,createdby,updated,updatedby,access_group) VALUES
|
||||
('Access Element','access_element','Auto-scanned: access_element',1,'2026-01-18 18:49:49','0','2026-01-18 18:49:49',NULL,'Views'),
|
||||
('Access Element Manage','access_element_manage','Auto-scanned: access_element_manage',1,'2026-01-18 18:49:49','0','2026-01-18 18:49:49',NULL,'Views'),
|
||||
@@ -921,5 +923,14 @@ WHERE warranty_date IS NOT NULL;
|
||||
|
||||
alter table users add refreshkey varchar(255);
|
||||
|
||||
UPDATE taxes SET eu = 1 WHERE country IN (
|
||||
'Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Cyprus', 'Czech Republic',
|
||||
'Denmark', 'Estonia', 'Finland', 'France', 'Germany', 'Greece',
|
||||
'Hungary', 'Ireland', 'Italy', 'Latvia', 'Lithuania', 'Luxembourg',
|
||||
'Malta', 'Netherlands', 'Poland', 'Portugal', 'Romania', 'Slovakia',
|
||||
'Slovenia', 'Spain', 'Sweden'
|
||||
);
|
||||
|
||||
UPDATE taxes SET rate = 0.00 WHERE eu = 0 OR eu IS NULL;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
@@ -5627,12 +5627,12 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') {
|
||||
// Extract customer data
|
||||
$customer = $invoice_data['customer'] ?? [];
|
||||
$customer_email = $customer['email'] ?? $invoice_data['payer_email'] ?? '';
|
||||
$customer_name = trim(($customer['first_name'] ?? '') . ' ' . ($customer['last_name'] ?? ''));
|
||||
$customer_address = $customer['address_street'] ?? '';
|
||||
$customer_city = $customer['address_city'] ?? '';
|
||||
$customer_state = $customer['address_state'] ?? '';
|
||||
$customer_zip = $customer['address_zip'] ?? '';
|
||||
$customer_country = $customer['address_country'] ?? '';
|
||||
$customer_name = $customer['name'] ?? trim(($customer['first_name'] ?? '') . ' ' . ($customer['last_name'] ?? ''));
|
||||
$customer_address = $customer['street'] ?? $customer['address_street'] ?? '';
|
||||
$customer_city = $customer['city'] ?? $customer['address_city'] ?? '';
|
||||
$customer_state = $customer['state'] ?? $customer['address_state'] ?? '';
|
||||
$customer_zip = $customer['zip'] ?? $customer['address_zip'] ?? '';
|
||||
$customer_country = $customer['country'] ?? $customer['address_country'] ?? '';
|
||||
|
||||
// Extract transaction data
|
||||
$pricing = $invoice_data['pricing'] ?? [];
|
||||
@@ -5744,6 +5744,54 @@ function generateSoftwareInvoice($invoice_data, $order_id, $language = 'US') {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate VAT note based on country and VAT number
|
||||
$vat_note = '';
|
||||
$customer_vat_number = $customer['vat_number'] ?? '';
|
||||
|
||||
// Load countries array
|
||||
include dirname(__FILE__,2).'/settings/countries.php';
|
||||
|
||||
debuglog("Customer Country: " . ($customer_country ?? 'NULL'));
|
||||
debuglog("Customer VAT: " . ($customer_vat_number ?? 'NULL'));
|
||||
|
||||
if (!empty($customer_country) && isset($countries)) {
|
||||
// Find country data
|
||||
$country_data = null;
|
||||
foreach ($countries as $country_info) {
|
||||
if ($country_info['country'] === $customer_country) {
|
||||
$country_data = $country_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($country_data) {
|
||||
$is_eu = $country_data['eu'] === 1;
|
||||
$is_netherlands = $customer_country === 'Netherlands';
|
||||
|
||||
if ($is_netherlands) {
|
||||
// Netherlands: no special note needed
|
||||
$vat_note = '';
|
||||
} elseif ($is_eu) {
|
||||
if (!empty($customer_vat_number)) {
|
||||
// EU with VAT number: reverse charge
|
||||
$vat_note = 'Reverse charge VAT';
|
||||
} else {
|
||||
// EU without VAT number: local VAT
|
||||
$vat_note = 'Local VAT';
|
||||
}
|
||||
} else {
|
||||
// Non-EU: out of scope
|
||||
$vat_note = 'Out of scope of EU VAT. Buyer is responsible for local taxes and duties';
|
||||
}
|
||||
|
||||
debuglog("VAT Note set to: " . ($vat_note ?: 'EMPTY'));
|
||||
} else {
|
||||
debuglog("Country NOT found in array: " . $customer_country);
|
||||
}
|
||||
} else {
|
||||
debuglog("Empty customer_country or countries not loaded");
|
||||
}
|
||||
|
||||
// Build HTML for PDF and EMAIL
|
||||
include dirname(__FILE__,2).'/assets/mail/email_template_invoice.php';
|
||||
include dirname(__FILE__,2).'/assets/mail/pdf_template_invoice.php';
|
||||
@@ -5816,7 +5864,8 @@ function generateCountriesFile($token){
|
||||
foreach($taxes as $tax){
|
||||
$countries[$tax['id']] = [
|
||||
'country' => $tax['country'] ?? '',
|
||||
'taxes' => $tax['rate'] ?? 0
|
||||
'taxes' => $tax['rate'] ?? 0,
|
||||
'eu' => $tax['eu'] ?? 0
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5826,7 +5875,7 @@ function generateCountriesFile($token){
|
||||
$fileContent .= "// Generated on: " . date('Y-m-d H:i:s') . "\n\n";
|
||||
$fileContent .= "\$countries = [\n";
|
||||
foreach($countries as $id => $data){
|
||||
$fileContent .= " " . $id . " => ['country' => '" . addslashes($data['country']) . "', 'taxes' => " . $data['taxes'] . "],\n";
|
||||
$fileContent .= " " . $id . " => ['country' => '" . addslashes($data['country']) . "', 'taxes' => " . $data['taxes'] . ",'eu' => " . $data['eu'] . "],\n";
|
||||
}
|
||||
$fileContent .= "];\n";
|
||||
|
||||
|
||||
@@ -122,14 +122,24 @@ $message .= '</tbody>
|
||||
</tr>';
|
||||
|
||||
if ($tax_amount > 0) {
|
||||
$tax_percentage = ($subtotal > 0) ? round(($tax_amount / $subtotal) * 100, 2) : 0;
|
||||
$vat_label = htmlspecialchars($lbl_tax) . ' (' . $tax_percentage . '%)';
|
||||
if (!empty($vat_note)) {
|
||||
$vat_label .= ' <small style="font-size: 11px; color: #888;">(' . htmlspecialchars($vat_note) . ')</small>';
|
||||
}
|
||||
$message .= '<tr>
|
||||
<td style="text-align: left; padding: 5px 0;">' . htmlspecialchars($lbl_tax) . '</td>
|
||||
<td style="text-align: left; padding: 5px 0;">' . $vat_label . '</td>
|
||||
<td style="text-align: right; padding: 5px 0;">€ ' . number_format($tax_amount, 2) . '</td>
|
||||
</tr>';
|
||||
} else {
|
||||
$vat_label = 'VAT';
|
||||
if (!empty($vat_note)) {
|
||||
$vat_label .= ' <small style="font-size: 11px; color: #888;">(' . htmlspecialchars($vat_note) . ')</small>';
|
||||
}
|
||||
$vat_amount_display = !empty($vat_note) ? '€ 0.00' : 'included';
|
||||
$message .= '<tr>
|
||||
<td style="text-align: left; padding: 5px 0;">VAT</td>
|
||||
<td style="text-align: right; padding: 5px 0;">included</td>
|
||||
<td style="text-align: left; padding: 5px 0;">' . $vat_label . '</td>
|
||||
<td style="text-align: right; padding: 5px 0;">' . $vat_amount_display . '</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
|
||||
@@ -299,14 +299,23 @@ $pdf .= '</tbody>
|
||||
|
||||
if ($tax_amount > 0) {
|
||||
$tax_percentage = ($subtotal > 0) ? round(($tax_amount / $subtotal) * 100, 2) : 0;
|
||||
$vat_label = htmlspecialchars($lbl_tax) . ' (' . $tax_percentage . '%)';
|
||||
if (!empty($vat_note)) {
|
||||
$vat_label .= ' <small style="font-size: 9px; color: #666;">(' . htmlspecialchars($vat_note) . ')</small>';
|
||||
}
|
||||
$pdf .= '<div class="total-row">
|
||||
<div class="total-label">' . htmlspecialchars($lbl_tax) . ' (' . $tax_percentage . '%)</div>
|
||||
<div class="total-label">' . $vat_label . '</div>
|
||||
<div class="total-amount">€ ' . number_format($tax_amount, 2) . '</div>
|
||||
</div>';
|
||||
} else {
|
||||
$vat_label = 'VAT';
|
||||
if (!empty($vat_note)) {
|
||||
$vat_label .= ' <small style="font-size: 9px; color: #666;">(' . htmlspecialchars($vat_note) . ')</small>';
|
||||
}
|
||||
$vat_amount_display = !empty($vat_note) ? '€ 0.00' : 'included';
|
||||
$pdf .= '<div class="total-row">
|
||||
<div class="total-label">VAT</div>
|
||||
<div class="total-amount">included</div>
|
||||
<div class="total-label">' . $vat_label . '</div>
|
||||
<div class="total-amount">' . $vat_amount_display . '</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -1485,12 +1485,35 @@ function showPaymentModal(option) {
|
||||
// Function to calculate and update tax
|
||||
function updateTaxDisplay() {
|
||||
const selectedCountry = document.getElementById("paymentCountry").value;
|
||||
const vatNumber = document.getElementById("paymentVatNumber").value.trim();
|
||||
let taxRate = 0;
|
||||
let vatNote = '';
|
||||
|
||||
if (selectedCountry && typeof COUNTRIES !== 'undefined' && COUNTRIES) {
|
||||
const countryData = Object.values(COUNTRIES).find(c => c.country === selectedCountry);
|
||||
if (countryData) {
|
||||
taxRate = parseFloat(countryData.taxes) || 0;
|
||||
const isEU = countryData.eu === 1;
|
||||
const isNetherlands = selectedCountry === 'Netherlands';
|
||||
const countryTaxRate = parseFloat(countryData.taxes) || 0;
|
||||
|
||||
if (isNetherlands) {
|
||||
// Netherlands: always take the tax percentage
|
||||
taxRate = countryTaxRate;
|
||||
} else if (isEU) {
|
||||
if (vatNumber) {
|
||||
// EU with VAT number: 0% VAT, reverse charge
|
||||
taxRate = 0;
|
||||
vatNote = 'Reverse charge VAT';
|
||||
} else {
|
||||
// EU without VAT number: use country VAT
|
||||
taxRate = countryTaxRate;
|
||||
vatNote = 'Local VAT';
|
||||
}
|
||||
} else {
|
||||
// Non-EU: use country tax percentage (usually 0)
|
||||
taxRate = countryTaxRate;
|
||||
vatNote = 'Out of scope of EU VAT. Buyer is responsible for local taxes and duties';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1503,13 +1526,13 @@ function showPaymentModal(option) {
|
||||
|
||||
if (taxRate > 0) {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT (${taxRate}%):</span>
|
||||
<span>VAT (${taxRate}%)${vatNote ? ' <small style="font-size: 9px; color: #888;">(' + vatNote + ')</small>' : ''}:</span>
|
||||
<span style="font-weight: 600;">${currency} ${taxAmount.toFixed(2)}</span>
|
||||
`;
|
||||
} else {
|
||||
taxDisplay.innerHTML = `
|
||||
<span>VAT:</span>
|
||||
<span style="font-weight: 600;">-</span>
|
||||
<span>VAT${vatNote ? ' <small style="font-size: 9px; color: #888;">(' + vatNote + ')</small>' : ''}:</span>
|
||||
<span style="font-weight: 600;">${vatNote ? '0%' : '-'}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1541,8 +1564,9 @@ function showPaymentModal(option) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener to country select to update tax
|
||||
// Add event listeners to country select and VAT number to update tax
|
||||
document.getElementById("paymentCountry").addEventListener('change', updateTaxDisplay);
|
||||
document.getElementById("paymentVatNumber").addEventListener('input', updateTaxDisplay);
|
||||
|
||||
// Close modal on cancel
|
||||
document.getElementById("cancelPayment").onclick = () => {
|
||||
|
||||
Reference in New Issue
Block a user