Developing a Multi-Currency Module for 1C-Bitrix
Bitrix has built-in currency support through the currency module: the b_currency table, rates in b_currency_rate, the bitrix:currency.rates component. At first glance — a ready-made solution. In practice, questions arise that the standard mechanism does not solve: automatic exchange rate updates from the Russian Central Bank or ECB, displaying prices in the visitor's currency with detection by IP, fixing the rate at the time of purchase, managing prices in different currencies independently (not through conversion from the base currency).
Module Architecture
The vendor.multicurrency module builds on top of the standard currency, not replacing it. The base currency is the ruble, rates are stored in the standard b_currency_rate. The module adds:
- Automatic rate updates from external sources
- User currency detection
- Rate fixing in the order
- Independent prices in currencies (not conversion)
Automatic Rate Updates
class RateUpdater
{
public function updateFromCbRu(): void
{
$xml = simplexml_load_file('https://www.cbr.ru/scripts/XML_daily.asp');
foreach ($xml->Valute as $valute) {
$code = (string)$valute->CharCode;
$rate = (float)str_replace(',', '.', (string)$valute->Value);
$nominal = (int)$valute->Nominal;
// Rate in Bitrix — how many rubles per 1 unit of foreign currency
$ratePerUnit = $rate / $nominal;
\CCurrencyRates::SetRatesList(SITE_ID, [[
'CURRENCY' => $code,
'RATE' => $ratePerUnit,
'RATE_CNT' => 1,
'DATE_RATE' => date('d.m.Y'),
]]);
}
}
}
The agent runs every 4 hours. The exchange rate source (Russian Central Bank, ECB, NBU, fixer.io) is a module settings parameter. A markup on the rate in percentage (bank spread) is supported.
User Currency Detection
Priority of detection:
- Explicit user choice (cookie
user_currency) - Detection by IP using MaxMind GeoIP2 or ip-api.com:
RU→ RUB,BY→ BYB/RUB,DE/FR/...→ EUR - Browser
Accept-Languageheader - Default currency (from module settings)
$currency = CurrencyDetector::detect(
$_COOKIE['user_currency'] ?? null,
$_SERVER['REMOTE_ADDR'],
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''
);
// Save in session for current request
$_SESSION['CURRENT_CURRENCY'] = $currency;
Price Display
The helper component vendor:multicurrency.price takes a price in the base currency (rubles) and displays it in the user's current currency:
// Conversion through standard Bitrix method
$priceInUserCurrency = \CCurrencyRates::ConvertCurrency($priceRub, 'RUB', $userCurrency);
$formatted = \CCurrencyLang::CurrencyFormat($priceInUserCurrency, $userCurrency, true);
// → "€ 149,90"
The currency switcher is the vendor:multicurrency.switcher component. When selecting a currency — record to cookie and AJAX redraw price blocks without full page reload.
Independent Prices in Currencies
For B2B scenarios, sometimes prices manually set in each currency are needed (not conversion from rubles, since prices may not match the rate). In this case, prices are stored in additional Bitrix price types:
- Price type
BASE(RUB) — standard - Price type
EUR_PRICE— price in euros, set manually - Price type
USD_PRICE— price in dollars
The module adds logic: if a separate price type exists for the current currency — use it, otherwise — convert from the base.
Rate Fixing in the Order
The exchange rate at the time of the order is fixed in b_sale_order.USER_DESCRIPTION (or in a separate HL-block):
AddEventHandler('sale', 'OnBeforeSaleOrderAdd', ['\Vendor\Multicurrency\OrderHandler', 'fixRate']);
public static function fixRate(\Bitrix\Main\Event $event): void
{
$order = $event->getParameter('ENTITY');
$currency = $_SESSION['CURRENT_CURRENCY'] ?? 'RUB';
$rate = \CCurrencyRates::GetConvertFactor('RUB', $currency);
$order->setField('CURRENCY', $currency);
// Save rate in order's additional field
OrderMetaTable::add(['ORDER_ID' => 0, 'CURRENCY' => $currency, 'RATE' => $rate]);
}
The fixed rate is needed for correct display of the order amount in the history and for accounting documents.
Development Timeline
| Stage | Duration |
|---|---|
| Automatic rate updates, agent | 1 day |
| User currency detection (GeoIP) | 1 day |
| Currency switcher, cookie | 1 day |
| AJAX price redraw | 1 day |
| Independent price types by currency | 1 day |
| Rate fixing in the order | 1 day |
| Formatting and localization | 0.5 day |
| Testing | 0.5 day |
Total: 7 working days. Adding additional rate sources (NBU, National Bank of Belarus) — 0.5 day per source.







