Setting Up Tax Rates by Region in 1C-Bitrix
Store operates in Belarus and Russia with different VAT rates. Or sells digital goods in EU where VAT depends on buyer's country. Standard single rate for entire catalog doesn't work here — need regional logic.
Tax groups
Bitrix implements regional rates through tax groups mechanism. Table b_sale_tax stores tax groups, b_sale_tax_rate — rates within groups tied to region.
Structure of b_sale_tax_rate: TAX_ID (group reference), COUNTRY_ID, REGION, CITY, RATE, IS_PERCENT (Y/N), IS_IN_PRICE (Y/N — analog of VAT_INCLUDED), ACTIVE.
Creating tax group with regional rates:
// Create group
$taxResult = \Bitrix\Sale\Tax::add([
'NAME' => 'VAT',
'ACTIVE' => 'Y',
'LID' => 's1',
]);
$taxId = $taxResult->getId();
// Rate for Belarus
\Bitrix\Sale\TaxRate::add([
'TAX_ID' => $taxId,
'COUNTRY_ID' => 'BY',
'RATE' => 20,
'IS_PERCENT' => 'Y',
'IS_IN_PRICE' => 'Y',
'ACTIVE' => 'Y',
]);
// Rate for Russia
\Bitrix\Sale\TaxRate::add([
'TAX_ID' => $taxId,
'COUNTRY_ID' => 'RU',
'RATE' => 20,
'IS_PERCENT' => 'Y',
'IS_IN_PRICE' => 'Y',
'ACTIVE' => 'Y',
]);
Binding taxes to order properties
Determining applicable rate happens when basket is calculated. Bitrix reads buyer address from order properties (b_sale_order_props_value) — looks for property with type LOCATION or TEXT with country/region code.
Binding tax group to store — configuration in /bitrix/admin/sale_tax.php. Each group can be active for specific site (LID). When calculating basket, sale module calls \Bitrix\Sale\Tax::getList() filtered by LID and applies first matching rate by country/region from address.
Determining buyer's region
Problem with the mechanism: buyer doesn't always fill address before basket calculation. For automatic region determination by IP, the seo module is used — class \Bitrix\Seo\Ip2Location. It queries geolocation service and returns country/region.
Handler for auto-substituting country in basket:
AddEventHandler('sale', 'OnSaleBasketBeforeSaved', function($basket) {
$order = $basket->getOrder();
if (!$order) return;
$ip = $_SERVER['REMOTE_ADDR'];
$location = \Bitrix\Seo\Ip2Location::getLocationByIp($ip);
if ($location && $location['COUNTRY_CODE']) {
// set order property with country code
$propertyCollection = $order->getPropertyCollection();
$prop = $propertyCollection->getItemByOrderPropertyCode('COUNTRY');
if ($prop) {
$prop->setValue($location['COUNTRY_CODE']);
}
}
});
Taxes and prices: display
If catalog prices include VAT (VAT_INCLUDED = Y), and regional rate is lower (e.g., 0% for export), basket should recalculate prices. Bitrix does this automatically via tax mechanism in sale module, but only if IS_IN_PRICE = Y in b_sale_tax_rate and VAT_INCLUDED = Y in b_catalog_product are aligned.
Mismatch of these fields — main source of incorrect VAT with regional rates. Verification query to find unaligned products:
SELECT cp.IBLOCK_ELEMENT_ID, cp.VAT_INCLUDED, cv.RATE
FROM b_catalog_product cp
LEFT JOIN b_catalog_vat cv ON cp.VAT_ID = cv.ID
WHERE cp.VAT_ID IS NOT NULL
AND cp.VAT_INCLUDED != 'Y';
All found items must be aligned to single rule before setting up regional rates.







