Add Reverse Charge VAT Notes in WHMCS invoices for EU Customers
As a business working with EU clients, complying with VAT regulations is essential. The reverse charge mechanism ensures that VAT responsibility is shifted to the client, avoiding the need for suppliers to register in every EU country.
If you’re using WHMCS, you can automate this process with hooks and scripts to streamline compliance. This article walks you through creating a dynamic VAT reverse charge solution for WHMCS, ensuring your invoices meet EU requirements while saving you time.
Why Automate Reverse Charge Notes in WHMCS?
The reverse charge mechanism requires specific invoice notes, such as mentioning that the transaction is not taxable under domestic VAT laws and that the customer must account for VAT. Doing this manually for each invoice is inefficient, error-prone, and inconsistent.
- Ensure Compliance: Always meet EU VAT invoicing requirements.
- Save Time: Automate note creation for new and old invoices.
- Eliminate Errors: Reduce human oversight in maintaining compliance.
Automating Reverse Charge Notes for New Invoices
To dynamically apply reverse charge VAT notes to new invoices in WHMCS, we’ll use a hook that triggers when invoices are created.
Script: add_reverse_vat_tax_to_invoices.php
Pre-requirements Check for WHMCS VAT Automation
Before implementing VAT reverse charge automation in WHMCS, it’s important to ensure that your system has the necessary fields and data configured correctly. This guide walks you through how to check for required variables using SQL queries and a simple PHP script.
Pre-requirements Checklist
- Custom Field for VAT Numbers: Ensure a custom field exists for storing VAT numbers.
- Client Data Requirements: Each client must have:
- A valid EU country code.
- Tax-exempt status enabled.
- A VAT number (if applicable).
- Currency Mapping: Ensure the
currency
field intblclients
maps correctly totblcurrencies
.
SQL Queries for Pre-requirements Check
1. Check for VAT Custom Field
SELECT id, fieldname, description FROM tblcustomfields WHERE fieldname LIKE '%VAT%';
Expected Output: You should see a custom field like this:
| id | fieldname | description | |-----|-----------|--------------------------------------| | 10 | VAT | Enter your VAT number (if applicable) |
2. Check Client Information
SELECT id, firstname, lastname, country, taxexempt, currency FROM tblclients WHERE id = 1;
Expected Output: Verify that the client has a valid EU country code, tax-exempt status, and a currency ID.
| id | firstname | lastname | country | taxexempt | currency | |-----|-----------|----------|---------|-----------|----------| | 1 | John | Doe | FR | 1 | 1 |
3. Check VAT Field Value
SELECT value FROM tblcustomfieldsvalues WHERE fieldid = 10 AND relid = 1;
Expected Output: Ensure the VAT number is present for the client.
| value | |-----------------| | FR123456789 |
How to Add a VAT Field (If Missing)
- Log in to WHMCS Admin Area.
- Navigate to Setup > Custom Client Fields.
- Click Add New Field and configure:
- Field Name: VAT
- Type: Text Box
- Description: Enter your VAT number (if applicable).
- Display Order: 1 (or any appropriate value).
- Click Save.
Example Pre-requirements Verification Script
Use this PHP script to automate the pre-requirements check:
<?php
require_once __DIR__ . '/init.php';
use WHMCS\Database\Capsule;
$userId = 1; // Example user ID
$vatField = Capsule::table('tblcustomfields')->where('fieldname', 'LIKE', '%VAT%')->first();
if ($vatField) {
echo "VAT Field Found: ID {$vatField->id}, Name: {$vatField->fieldname}\n";
} else {
echo "No VAT Field Found. Please add a VAT custom field.\n";
exit;
}
$client = Capsule::table('tblclients')->where('id', $userId)->first();
if ($client) {
echo "Client Found: {$client->firstname} {$client->lastname}, Country: {$client->country}, Tax Exempt: {$client->taxexempt}\n";
} else {
echo "Client Not Found. Please check the User ID.\n";
exit;
}
$vatNumber = Capsule::table('tblcustomfieldsvalues')->where('fieldid', $vatField->id)->where('relid', $userId)->value('value');
if ($vatNumber) {
echo "VAT Number Found: $vatNumber\n";
} else {
echo "No VAT Number Found for Client ID: $userId\n";
}
How to Use the Script
- Save the script as
pre_requirements_check.php
in the WHMCS root directory. - Run it via the command line:
php pre_requirements_check.php
- Review the output to ensure all conditions are met.
The WHMCS php hook script that add automatic note for reverse vat charge
!!! ALWAYS RUN SQL BACKUP BEFORE RUNNING THIS SCRIPT !!!
<?php
/**
* Private Devops LTD - WHMCS VAT Reverse Charge Hook
* ---------------------------------------------------
* This hook automatically applies reverse charge notes to invoices
* for eligible EU and GB clients based on VAT regulations at the time
* of invoice creation.
*
* Author: Private Devops LTD
* Website: https://privatedevops.com
*
* Description:
* ------------
* This hook ensures that invoices created in WHMCS comply with VAT regulations for
* B2B and B2C transactions across the EU and the UK. It dynamically applies reverse
* charge notes or VAT charges to invoices based on the client's country, tax exemption
* status, and VAT registration. For eligible B2B transactions, no VAT is charged,
* and the responsibility for VAT reporting shifts to the client (reverse charge).
*
* Key Features:
* -------------
* - Automatically triggered during invoice creation in WHMCS.
* - Adds reverse charge notes for EU and GB clients with valid VAT numbers.
* - Ensures VAT compliance for both B2B (reverse charge) and B2C (standard VAT) scenarios.
* - Fetches company country code dynamically from WHMCS settings (`TaxEUHomeCountry`).
* - Flexible configuration for VAT custom field ID and note templates.
* - Detailed logging for debugging and activity tracking in WHMCS.
*
* How to Use:
* -----------
* 1. Save this script in the `includes/hooks/` directory of your WHMCS installation.
* 2. Update the `$customFieldId` variable to match the VAT custom field ID in your WHMCS:
* - Run: `SELECT id, fieldname FROM tblcustomfields WHERE fieldname LIKE '%VAT%';`
* - Replace the default value of `$customFieldId` with the retrieved ID.
* 3. Customize the `$noteTemplateEU` and `$noteTemplateGB` variables for EU and GB notes.
* 4. Test the hook by creating new invoices for clients from EU countries or GB.
* 5. Monitor the WHMCS Activity Log (Utilities > Logs > Activity Log) for hook execution details.
*
* Requirements:
* -------------
* - WHMCS must be configured with a VAT custom field or the native `vat` field.
* - Clients must:
* - Have a valid VAT number (stored in the configured custom field or native field).
* - Be marked as tax-exempt (for reverse charge eligibility).
* - Reside in an EU country or GB for processing.
* - Ensure your company country code is correctly set in WHMCS (`TaxEUHomeCountry`).
*
* Key Regulations Referenced:
* ----------------------------
* - **EU VAT Directive 2006/112/EC (Article 196)**:
* - Governs reverse charge mechanisms for B2B cross-border services within the EU.
* - **UK VAT Act 1994**:
* - Governs reverse charge rules for services supplied to UK businesses post-Brexit.
* - **Local VAT Rules (e.g., Bulgaria)**:
* - Applied for B2C transactions where no valid VAT number is provided.
*
* Notes:
* ------
* - This hook applies only to invoices created after its implementation.
* - Skips clients without valid VAT numbers or tax exemption status.
* - Provides activity log entries for debugging and tracking.
* - Ideal for maintaining compliance with VAT regulations in real time.
*/
use WHMCS\Database\Capsule;
// Configuration: Update this with your VAT custom field ID
$customFieldId = 10; // Change this to match your VAT custom field ID
// Array of EU country codes (including GB for B2B logic)
$euCountries = [
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR',
'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI',
'SK', 'GB'
];
// Configuration: Note templates for EU and GB
$noteTemplateEU = "VAT (0%): 0.00 {currency}\nDomestic turnover is not taxable, your VAT registration number is: {vat}.\nThis transaction complies with Article 196 of the EU VAT Directive 2006/112/EC (Reverse Charge).";
$noteTemplateGB = "No VAT charged under UK reverse charge rules. Your VAT registration number is: {vat}.\nThis transaction complies with the UK VAT Act 1994 and post-Brexit domestic reverse charge regulations.";
add_hook('InvoiceCreation', 1, function ($vars) use ($customFieldId, $euCountries, $noteTemplateEU, $noteTemplateGB) {
$invoiceId = $vars['invoiceid'];
// Fetch company country code from WHMCS TaxEUHomeCountry setting
$companyCountryCode = Capsule::table('tblconfiguration')
->where('setting', 'TaxEUHomeCountry')
->value('value');
if (!$companyCountryCode) {
logActivity("VAT Hook Debug: Failed to retrieve company country code from TaxEUHomeCountry.");
return;
}
// Fetch invoice details to get the user ID
$invoice = Capsule::table('tblinvoices')
->where('id', $invoiceId)
->first(['userid']);
if (!$invoice) {
logActivity("VAT Hook Debug: No invoice found for Invoice ID: $invoiceId");
return;
}
$userId = $invoice->userid;
// Fetch client details
$clientDetails = Capsule::table('tblclients')
->where('id', $userId)
->first(['country', 'taxexempt', 'currency']);
if (!$clientDetails) {
logActivity("VAT Hook Debug: No client found for User ID: $userId");
return;
}
$clientCountry = $clientDetails->country;
// **EARLY CHECK**: Skip clients not in the EU or GB immediately
if (!in_array($clientCountry, $euCountries)) {
logActivity("VAT Hook Debug: Skipped User ID: $userId (Country: $clientCountry) - Not in EU or GB.");
return;
}
// Retrieve VAT number now that we've confirmed EU/GB
$vatNumber = Capsule::table('tblcustomfieldsvalues')
->where('fieldid', $customFieldId)
->where('relid', $userId)
->value('value');
if (empty($vatNumber)) {
$vatNumber = Capsule::table('tblclients')
->where('id', $userId)
->value('vat');
}
if (!$vatNumber) {
logActivity("VAT Hook Debug: No VAT Number found for User ID: $userId.");
return;
}
$isTaxExempt = $clientDetails->taxexempt;
$currencyId = $clientDetails->currency;
// Fetch the client currency
$currency = Capsule::table('tblcurrencies')
->where('id', $currencyId)
->value('code');
// Log variables for debugging
logActivity("VAT Hook Debug: Invoice ID: $invoiceId, User ID: $userId, Country: $clientCountry, Company Country: $companyCountryCode, Tax Exempt: $isTaxExempt, VAT Number: $vatNumber");
// Check conditions for adding the reverse charge note
if (
in_array($clientCountry, $euCountries) &&
$clientCountry !== $companyCountryCode &&
!empty($isTaxExempt) &&
!empty($vatNumber)
) {
// Use GB-specific note for UK clients
$note = ($clientCountry === 'GB')
? str_replace('{vat}', $vatNumber, $noteTemplateGB)
: str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateEU);
// Update the invoice with the note
Capsule::table('tblinvoices')
->where('id', $invoiceId)
->update(['notes' => $note]);
logActivity("VAT Hook Debug: Added reverse charge note to Invoice ID: $invoiceId for User ID: $userId");
} else {
logActivity("VAT Hook Debug: Conditions not met for Invoice ID: $invoiceId. Country: $clientCountry, Tax Exempt: $isTaxExempt, VAT: $vatNumber");
}
});
How It Works:
- The hook triggers on invoice creation.
- It checks the client’s country, VAT status, and VAT number.
- If all conditions are met, it appends a reverse charge note to the invoice.
Bulk update script for Existing Invoices
What about old invoices? Use a one-time script to iterate through your invoices and update their notes if they meet the reverse charge criteria.
Make sure you know what you do and TRIPLE (if need) verify the variables for vat field ID
!!! ALWAYS RUN SQL BACKUP BEFORE RUNNING THIS SCRIPT !!!
Script: update_old_invoices.php
<?php
/**
* Private Devops LTD - WHMCS VAT Reverse Charge Hook
* ---------------------------------------------------
* This hook automatically applies reverse charge notes to invoices
* for eligible EU and GB clients based on VAT regulations at the time
* of invoice creation.
*
* Author: Private Devops LTD
* Website: https://privatedevops.com
*
* Description:
* ------------
* This hook ensures that invoices created in WHMCS comply with VAT regulations for
* B2B and B2C transactions across the EU and the UK. It dynamically applies reverse
* charge notes or VAT charges to invoices based on the client's country, tax exemption
* status, and VAT registration. For eligible B2B transactions, no VAT is charged,
* and the responsibility for VAT reporting shifts to the client (reverse charge).
*
* Key Features:
* -------------
* - Automatically triggered during invoice creation in WHMCS.
* - Adds reverse charge notes for EU and GB clients with valid VAT numbers.
* - Ensures VAT compliance for both B2B (reverse charge) and B2C (standard VAT) scenarios.
* - Fetches company country code dynamically from WHMCS settings (`TaxEUHomeCountry`).
* - Flexible configuration for VAT custom field ID and note templates.
* - Detailed logging for debugging and activity tracking in WHMCS.
*
* How to Use:
* -----------
* 1. Save this script in the `includes/hooks/` directory of your WHMCS installation.
* 2. Update the `$customFieldId` variable to match the VAT custom field ID in your WHMCS:
* - Run: `SELECT id, fieldname FROM tblcustomfields WHERE fieldname LIKE '%VAT%';`
* - Replace the default value of `$customFieldId` with the retrieved ID.
* 3. Customize the `$noteTemplateEU` and `$noteTemplateGB` variables for EU and GB notes.
* 4. Test the hook by creating new invoices for clients from EU countries or GB.
* 5. Monitor the WHMCS Activity Log (Utilities > Logs > Activity Log) for hook execution details.
*
* Requirements:
* -------------
* - WHMCS must be configured with a VAT custom field or the native `vat` field.
* - Clients must:
* - Have a valid VAT number (stored in the configured custom field or native field).
* - Be marked as tax-exempt (for reverse charge eligibility).
* - Reside in an EU country or GB for processing.
* - Ensure your company country code is correctly set in WHMCS (`TaxEUHomeCountry`).
*
* Key Regulations Referenced:
* ----------------------------
* - **EU VAT Directive 2006/112/EC (Article 196)**:
* - Governs reverse charge mechanisms for B2B cross-border services within the EU.
* - **UK VAT Act 1994**:
* - Governs reverse charge rules for services supplied to UK businesses post-Brexit.
* - **Local VAT Rules (e.g., Bulgaria)**:
* - Applied for B2C transactions where no valid VAT number is provided.
*
* Notes:
* ------
* - This hook applies only to invoices created after its implementation.
* - Skips clients without valid VAT numbers or tax exemption status.
* - Provides activity log entries for debugging and tracking.
* - Ideal for maintaining compliance with VAT regulations in real time.
*/
require_once __DIR__ . '/init.php';
use WHMCS\Database\Capsule;
// Configuration
$customFieldId = 10; // Change this to match your VAT custom field ID
// Configuration: Note templates for EU and GB
$noteTemplateEU = "VAT (0%): 0.00 {currency}\nDomestic turnover is not taxable, your VAT registration number is: {vat}.\nThis transaction complies with Article 196 of the EU VAT Directive 2006/112/EC (Reverse Charge). Under this mechanism, the buyer is responsible for reporting VAT in their respective EU country.";
$noteTemplateGB = "No VAT charged under UK reverse charge rules. Your VAT registration number is: {vat}.\n This transaction complies with the UK VAT Act 1994 and post-Brexit domestic reverse charge regulations. Under these rules, the buyer is required to self-account for VAT.";
// Fetch company country code from WHMCS settings
$companyCountryCode = Capsule::table('tblconfiguration')
->where('setting', 'TaxEUHomeCountry')
->value('value');
if (!$companyCountryCode) {
echo "Error: Failed to retrieve company country code from TaxEUHomeCountry.\n";
exit;
}
// Array of EU country codes (including GB for B2B rules)
$euCountries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB'];
// Ensure $euCountries is an array
if (!is_array($euCountries) || empty($euCountries)) {
echo "Error: Invalid EU country list.\n";
exit;
}
try {
echo "Starting invoice updates...\n";
$invoices = Capsule::table('tblinvoices')
->whereIn('status', ['Unpaid', 'Paid'])
->get();
foreach ($invoices as $invoice) {
$invoiceId = $invoice->id;
$userId = $invoice->userid;
// Fetch client details
$clientDetails = Capsule::table('tblclients')
->where('id', $userId)
->first(['country', 'taxexempt', 'currency']);
if (!$clientDetails) {
echo "No client found for User ID: $userId. Skipping invoice ID: $invoiceId.\n";
continue;
}
$clientCountry = $clientDetails->country;
$isTaxExempt = $clientDetails->taxexempt;
$currencyId = $clientDetails->currency;
// **EARLY CHECK**: If client is not in EU or GB, skip now
if (!in_array($clientCountry, $euCountries)) {
echo "User ID: $userId (Country: $clientCountry) not in EU/GB. Skipping invoice ID: $invoiceId.\n";
continue;
}
// Retrieve VAT number from custom field only if in EU/GB
$vatNumber = Capsule::table('tblcustomfieldsvalues')
->where('fieldid', $customFieldId)
->where('relid', $userId)
->value('value');
// Fallback to native VAT field if custom field is empty
if (empty($vatNumber)) {
$vatNumber = Capsule::table('tblclients')
->where('id', $userId)
->value('vat');
}
// Fetch the currency code
$currency = Capsule::table('tblcurrencies')
->where('id', $currencyId)
->value('code');
// Log details for debugging
echo "Checking invoice ID: $invoiceId, User ID: $userId, Country: $clientCountry, Tax Exempt: $isTaxExempt, VAT Number: $vatNumber, Currency: $currency\n";
// Process conditions after confirming EU/GB membership
if (
$clientCountry !== $companyCountryCode &&
!empty($isTaxExempt) &&
!empty($vatNumber)
) {
// Use different note templates for EU and GB
$note = ($clientCountry === 'GB')
? str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateGB)
: str_replace(['{currency}', '{vat}'], [$currency, $vatNumber], $noteTemplateEU);
// Update the invoice notes
Capsule::table('tblinvoices')
->where('id', $invoiceId)
->update(['notes' => $note]);
echo "Updated invoice ID: $invoiceId with reverse charge note.\n";
} else {
echo "Conditions not met for invoice ID: $invoiceId. Skipping.\n";
}
}
echo "Invoice updates completed.\n";
} catch (\Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
How to Use:
- Place this script in your WHMCS root directory. !!!! CREATE A SQL DB BACKUP !!!!
- Run via command line:
php update_old_invoices.php
- Monitor the output for updated invoices.
So ...
With these scripts, you can automate compliance with EU VAT regulations in WHMCS, saving time and reducing manual errors. Implement the hook for future invoices and use the one-time script to bring past invoices up to compliance.
For tailored solutions, contact Private Devops LTD—your trusted Private DevOps partner! 🚀
Browse the scripts from our GitHub repo
Visit Our Website
Need Expert Help?
We’re here to support you and manage your tasks.