Клиент набирает корзину на 5000 ₽, на checkout'e видит доставку 800 ₽ — и уходит. По данным Baymard Institute, 48% брошенных корзин — из-за неожиданной стоимости доставки. Мы проектируем калькуляторы, которые показывают точную сумму с выбором способа и срока — до оформления заказа. Результат: конверсия checkout'а растёт на 15–25%. В этой статье — как мы делаем такой калькулятор: от схемы данных до интеграции с СДЭК и Почтой России. Наш опыт — более 50 проектов в e-commerce, поэтому гарантируем рабочее решение, которое не потеряет клиентов на checkout.
Например, интернет-магазин электроники столкнулся с тем, что старый калькулятор выдавал стоимость только после ввода всех данных — и клиенты уходили. Мы внедрили мгновенный расчёт через кеш и PostgreSQL, и конверсия checkout выросла на 20% за неделю. Это типичный сценарий: без предварительного расчёта клиент не видит финальной цены, и корзина бросается.
Какие проблемы решает калькулятор доставки?
Неожиданные расходы. Клиент видит финальную стоимость до оформления — отказов меньше. Мы добавляем кеширование, чтобы расчёт шёл за 200–500 мс — это в 3 раза быстрее прямого запроса к API.
Неработающие API. Перевозчики меняют версии, падают лимиты — наша архитектура с интерфейсами изолирует сбои. Упал СДЭК — клиенту показываем тарифы из БД или Почту России.
Сложные правила. Бесплатная доставка от суммы, зоны, габаритный вес — всё закладывается в таблицу тарифов. Настраивается без кода через админку.
Почему расчёт через БД быстрее?
Таблицы зон и тарифов в PostgreSQL работают за 10–50 мс. Внешний API — 200–1000 мс. Кешируем результат на 5 минут, поэтому для повторных запросов (один и тот же город и вес) ответ приходит мгновенно.
Как избежать ошибок при интеграции API?
Главная ошибка — не учитывать габаритный вес. Курьерские службы могут брать оплату за объём, а не за реальный вес. Мы встраиваем проверку max(weight, length*width*height/5000). Вторая ошибка — отсутствие fallback-а: если API перевозчика не отвечает, показываем тарифы из БД. У нас это автоматически в DeliveryCalculatorService. Третья — медленный расчёт без кеширования: мы кешируем результат на 5 минут и используем debounce на фронтенде.
Почему мы выбираем PostgreSQL для тарифов?
PostgreSQL позволяет гибко управлять зонами и ставками через SQL-запросы. Мы используем массивы для регионов и JSONB для дополнительных правил. Это даёт производительность на уровне 10–50 мс на запрос, что критично для checkout. В отличие от внешних API, мы не зависим от их доступности и лимитов.
Как мы разрабатываем калькулятор
Этапы
- Аналитика — определяем перевозчиков, зоны доставки, правила бесплатной доставки. Собираем данные о среднем весе и габаритах товаров.
- Проектирование схемы — создаём таблицы
delivery_zonesиdelivery_rates. Проектируем интерфейсDeliveryProviderInterface. - Реализация сервиса — пишем
DeliveryCalculatorServiceс поддержкой кеширования и fallback'ом на БД. - Интеграция с API — подключаем СДЭК и Почту России через единый интерфейс. Каждая служба — отдельный класс.
- Фронтенд — React-компонент с подсказкой городов через DaData. Debounce 600 мс, чтобы не дёргать сервер.
- Тестирование — проверяем на реальных заказах, юнит-тесты для сервиса.
- Деплой — развёртываем на сервере, настраиваем кеширование.
Схема данных
CREATE TABLE delivery_zones (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
country CHAR(2) DEFAULT 'RU',
regions TEXT[], -- коды регионов ФИАС
cities TEXT[], -- коды городов КЛАДР
carrier_id INT REFERENCES carriers(id)
);
CREATE TABLE delivery_rates (
id BIGSERIAL PRIMARY KEY,
carrier_id INT REFERENCES carriers(id),
zone_id BIGINT REFERENCES delivery_zones(id),
method VARCHAR(50), -- 'courier', 'pickup', 'post'
weight_from_g INT DEFAULT 0,
weight_to_g INT,
price NUMERIC(10,2) NOT NULL,
days_min SMALLINT,
days_max SMALLINT,
free_from NUMERIC(12,2), -- бесплатно при сумме от X
is_active BOOLEAN DEFAULT TRUE
);
Интеграция с СДЭК
class CdekDeliveryProvider implements DeliveryProviderInterface
{
private string $baseUrl = 'https://api.cdek.ru/v2';
public function calculate(DeliveryRequest $request, int $weightG): array
{
$token = $this->getToken();
$response = Http::withToken($token)
->post("{$this->baseUrl}/calculator/tarifflist", [
'type' => 1,
'currency' => 1,
'lang' => 'rus',
'from_location' => ['code' => config('cdek.from_city_code')],
'to_location' => ['address' => $request->destination->address],
'packages' => [[
'weight' => $weightG,
'length' => 30,
'width' => 20,
'height' => 10,
]],
]);
if (!$response->successful()) return [];
return collect($response->json('tariff_codes', []))
->map(fn($t) => new DeliveryOption(
carrierId: 'cdek',
method: $this->mapTariffToMethod($t['tariff_code']),
name: 'СДЭК — ' . $t['tariff_name'],
price: $t['delivery_sum'],
daysMin: $t['period_min'],
daysMax: $t['period_max'],
))
->toArray();
}
}
Почта России подключается по аналогии через класс RussianPostProvider.
Фронтенд-компонент
const DeliveryCalculator: React.FC<{ cartItems: CartItem[] }> = ({ cartItems }) => {
const [city, setCity] = useState('');
const [options, setOptions] = useState<DeliveryOption[]>([]);
const [loading, setLoading] = useState(false);
const calculate = useDebouncedCallback(async (cityValue: string) => {
if (cityValue.length < 3) return;
setLoading(true);
try {
const res = await api.post('/delivery/calculate', {
destination: cityValue,
items: cartItems.map(i => ({ product_id: i.id, quantity: i.qty })),
});
setOptions(res.data.options);
} finally {
setLoading(false);
}
}, 600);
return (
<div>
<input
placeholder="Введите город доставки"
value={city}
onChange={e => { setCity(e.target.value); calculate(e.target.value); }}
className="border rounded px-3 py-2 w-full"
/>
{loading && <p className="text-sm text-gray-400 mt-2">Считаем стоимость...</p>}
{options.length > 0 && (
<ul className="mt-3 space-y-2">
{options.map(opt => (
<li key={opt.id} className="flex justify-between items-center border rounded px-3 py-2">
<div>
<p className="font-medium">{opt.name}</p>
<p className="text-sm text-gray-500">{opt.daysMin}–{opt.daysMax} дней</p>
</div>
<span className="font-semibold">
{opt.isFree ? 'Бесплатно' : formatPrice(opt.price)}
</span>
</li>
))}
</ul>
)}
</div>
);
};
Сравнение служб доставки
| Параметр | СДЭК | Почта России |
|---|---|---|
| Типы доставки | Курьер, постамат, ПВЗ | Почтомат, отделение, курьер |
| Средний срок | 1-5 дней | 3-14 дней |
| Точность расчёта | Высокая (API v2) | Средняя (требуется указание индекса) |
| Поддержка габаритного веса | Да | Да (через объём) |
Типичные ошибки и как их избежать
Главная ошибка — не учитывать габаритный вес. Курьерские службы могут брать оплату за объём, а не за реальный вес. Мы встраиваем проверку max(weight, length*width*height/5000). Вторая ошибка — отсутствие fallback-а: если API перевозчика не отвечает, показываем тарифы из БД. У нас это автоматически в DeliveryCalculatorService. Третья — медленный расчёт без кеширования: мы кешируем результат на 5 минут и используем debounce на фронтенде.
Что входит в работу и сроки
Состав работ:
- Схема данных (таблицы зон и тарифов).
- Сервис вычисления DeliveryCalculatorService с поддержкой кеширования.
- Интеграция с двумя службами доставки: СДЭК и Почта России.
- Фронтенд-компонент на React/Next.js с подсказкой городов через DaData.
- API-эндпоинт с кешированием на 5 минут.
- Документация по настройке тарифов и добавлению новых перевозчиков.
- Поддержка после внедрения — 1 месяц гарантии.
Ориентировочные сроки по этапам:
| Этап | Длительность |
|---|---|
| Аналитика | 0.5–1 день |
| Проектирование схемы | 0.5 дня |
| Реализация сервиса | 1–1.5 дня |
| Интеграция API | 1–2 дня |
| Фронтенд | 1–2 дня |
| Тестирование | 0.5–1 день |
| Деплой | 0.5 дня |
Общий срок: от 3 до 5 рабочих дней в зависимости от количества перевозчиков и сложности тарифов.
Закажите разработку калькулятора доставки, чтобы ваши клиенты видели точную стоимость до оформления заказа. Свяжитесь с нами для расчёта вашего проекта.







