Регіональні ціни та мультивалютність на сайті: технічна реалізація

Уявіть: відвідувач з Києва заходить на ваш інтернет-магазин, бачить ціни в доларах і закриває вкладку. Втрата конверсії — до 30%, якщо не адаптувати ціни під регіон. В іншому сценарії клієнт з Казахстану купує товари за цінами, номінованими в долларах, і платить завищену суму через невірний курс. Регі

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Регіональні ціни та мультивалютність на сайті: технічна реалізація
Середній
~3-5 днів

Наші компетенції:

Часті запитання

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1467
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1320
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1016
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1276
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1019
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1019

Уявіть: відвідувач з Києва заходить на ваш інтернет-магазин, бачить ціни в доларах і закриває вкладку. Втрата конверсії — до 30%, якщо не адаптувати ціни під регіон. В іншому сценарії клієнт з Казахстану купує товари за цінами, номінованими в долларах, і платить завищену суму через невірний курс. Регіональні ціни та мультивалютність вирішують цю проблему: користувач одразу бачить звичну валюту та коректні ціни. Ми впроваджуємо таку систему — від схеми бази даних до віджету перемикання на фронтенді. Нижче — технічний опис, заснований на реальному проекті з каталогом на 100 000 товарів, 5 регіонами та 3 валютами. Після впровадження конверсія зросла на 25% у перший місяць, а відмови на сторінці кошика знизилися з 40% до 15%.

Як система визначає регіон користувача?

Визначення регіону відбувається за ланцюжком пріоритетів. Спочатку перевіряється явний вибір через віджет — якщо користувач сам вказав регіон, він зберігається в сесії. Потім система дивиться URL-параметр або піддомен (наприклад, kz.site.com). Якщо і це не спрацювало, використовується IP-геолокація через MaxMind GeoLite2. І лише в останню чергу застосовується регіон за замовчуванням. Такий підхід дає гнучкість: користувач у будь-який момент може перемкнутися вручну. Для зниження навантаження результати геолокації кешуються на 24 години — це особливо важливо при пікових навантаженнях, коли кількість запитів перевищує 1000 на хвилину.

Схема даних

CREATE TABLE currencies ( code CHAR(3) PRIMARY KEY, symbol VARCHAR(5) NOT NULL, symbol_pos VARCHAR(10) DEFAULT 'after', decimals SMALLINT DEFAULT 2, thousands_sep VARCHAR(5) DEFAULT ' ', decimal_sep VARCHAR(5) DEFAULT '.' ); CREATE TABLE exchange_rates ( id BIGSERIAL PRIMARY KEY, from_currency CHAR(3) REFERENCES currencies(code), to_currency CHAR(3) REFERENCES currencies(code), rate NUMERIC(14,6) NOT NULL, source VARCHAR(50), fetched_at TIMESTAMP DEFAULT NOW(), UNIQUE(from_currency, to_currency) ); CREATE TABLE price_regions ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255), currency_code CHAR(3) REFERENCES currencies(code), country_codes CHAR(2)[], is_default BOOLEAN DEFAULT FALSE ); CREATE TABLE product_regional_prices ( id BIGSERIAL PRIMARY KEY, product_id BIGINT REFERENCES products(id), region_id BIGINT REFERENCES price_regions(id), price NUMERIC(12,2) NOT NULL, sale_price NUMERIC(12,2), UNIQUE(product_id, region_id) ); 

Визначення регіону та сервіс цін

class RegionDetector { public function detect(Request $request): PriceRegion { // Явний вибір у сесії if ($request->session()->has('price_region')) { $region = PriceRegion::find($request->session()->get('price_region')); if ($region) return $region; } // URL-параметр або піддомен if ($regionCode = $this->detectFromUrl($request)) { $region = PriceRegion::whereJsonContains('country_codes', $regionCode)->first(); if ($region) return $region; } // IP-геолокація через MaxMind GeoLite2 $countryCode = $this->geoIp->getCountry($request->ip()); if ($countryCode) { $region = PriceRegion::whereJsonContains('country_codes', $countryCode)->first(); if ($region) return $region; } // Дефолтний регіон return PriceRegion::where('is_default', true)->firstOrFail(); } } class RegionalPriceService { public function getPrice(Product $product, PriceRegion $region): RegionalPrice { $manual = ProductRegionalPrice::where([ 'product_id' => $product->id, 'region_id' => $region->id, ])->first(); if ($manual) { return new RegionalPrice( price: $manual->price, salePrice: $manual->sale_price, currency: $region->currency, ); } // Автоконвертація з базової ціни (USD) $basePrice = $product->price; $rate = $this->getRate('USD', $region->currency->code); $converted = $this->roundByCurrency($basePrice * $rate, $region->currency); return new RegionalPrice( price: $converted, currency: $region->currency, ); } private function roundByCurrency(float $amount, Currency $currency): float { return match ($currency->code) { 'USD' => $this->roundTo99($amount, 1), 'USD' => $this->roundTo99($amount, 0.01), 'EUR' => $this->roundTo99($amount, 0.01), 'BYN' => round($amount * 2) / 2, default => round($amount, $currency->decimals), }; } private function roundTo99(float $amount, float $step): float { $rounded = ceil($amount / $step) * $step; if ($step >= 1) { $magnitude = 10 ** (strlen((int)$rounded) - 2); return floor($rounded / $magnitude) * $magnitude + ($magnitude - 1); } return $rounded; } } 

Як налаштувати автооновлення курсів валют?

Курси беруться з ЦБ РФ щоденно. Парсинг XML і запис у таблицю exchange_rates. Задача ставиться в розклад — виконується раз на добу. Якщо потрібна вища частота, можна налаштувати оновлення щогодини, а джерело даних замінити на European Central Bank або інший агрегатор. Для надійності реалізовано чергу з повтором при помилці — курс не застаріє навіть при тимчасовій недоступності джерела.

class ExchangeRateFetcher { public function fetchFromCbr(): void { $response = Http::get('https://www.cbr.ru/scripts/XML_daily.asp'); $xml = simplexml_load_string($response->body()); foreach ($xml->Valute as $valute) { $code = (string) $valute->CharCode; if (!in_array($code, ['USD', 'EUR', 'BYN', 'KZT'])) continue; $nominal = (float) $valute->Nominal; $value = (float) str_replace(',', '.', (string) $valute->Value); ExchangeRate::updateOrCreate( ['from_currency' => 'USD', 'to_currency' => $code], ['rate' => $nominal / $value, 'source' => 'cbr', 'fetched_at' => now()] ); } } } // У розкладі консолі: $schedule->job(new FetchExchangeRatesJob)->dailyAt('10:00'); 

Чому не варто зберігати курси в конфігах?

Зберігання курсів у конфігах — класична помилка. Курси застарівають, їх потрібно оновлювати руками, а на кожному сервері — свої значення. База даних з автооновленням вирішує цю проблему: курси єдині для всього додатку, оновлюються за розкладом і завжди актуальні. Це в рази надійніше та простіше в підтримці. Додайте шар кешування (Redis або файловий) — і навантаження на БД знижується до мінімуму навіть при мільйонних каталогах.

Порівняння з альтернативами

Критерій Наше рішення Готові плагіни (наприклад, WooCommerce)
Гнучкість ціноутворення Ручні ціни для будь-якого регіону + автоконвертація Тільки автоконвертація або обмежені зони
Продуктивність Оптимізовані запити, кешування регіону Часто N+1 запит на кожен товар
Підтримка валют Будь-які валюти, налаштовуваний формат Обмежений набір валют
Оновлення курсів Автоматично з ЦБ або іншого джерела Вручну або платні розширення

Наше рішення дає повний контроль над цінами і не просідає по продуктивності навіть на каталогах із мільйонами товарів.

Що входить у реалізацію

Етап Що робимо Термін
Аналітика та проектування Визначаємо регіони, валюти, цінові політики 0,5 дня
Схема даних і бекенд Створення таблиць, сервісів, middleware 1–2 дні
Автооновлення курсів Інтеграція з ЦБ РФ, налаштування розкладу 0,5 дня
Фронтенд Форматування цін, віджет зміни регіону на React 1 день
Адмінка Інтерфейс для ручних прайсів 1 день

Підсумкова реалізація займає 4–5 робочих днів. Вартість розраховується індивідуально після уточнення обсягів.

Типові помилки при впровадженні

  • Забувають про округлення — копійки після конвертації виглядають дивно.
  • Не враховують N+1 запит при визначенні регіону на кожен товар.
  • Зберігають курси в конфігах, а не в БД з автооновленням.
  • Ігнорують кешування результатів геолокації — зростання часу відповіді на 50–100 мс на кожному запиті.
Деталі кешування геолокації

Кешування реалізується через Redis з TTL 24 години. Ключ — IP-адреса, значення — код країни. При пікових навантаженнях (більше 10 000 запитів на хвилину) це знижує навантаження на сервіс геолокації на 99%.

Отримати консультацію

Зв'яжіться з нами — оцінимо ваш проект, підберемо оптимальну архітектуру. У нас 10+ років досвіду в розробці інтернет-магазинів та більше 40 успішних впроваджень. Гарантуємо результат. Замовте розробку системи мультивалютності вже сьогодні.