Разработка калькулятора доставки для интернет-магазина

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка калькулятора доставки для интернет-магазина
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка калькулятора доставки для интернет-магазина

Покупатель не должен добавлять товар в корзину, вводить адрес, нажимать «Оформить» и только на последнем шаге узнавать, что доставка стоит половину от заказа. Это одна из главных причин брошенных корзин. Калькулятор доставки решает эту проблему — показывает стоимость сразу, до начала оформления.

Что считает калькулятор

Стоимость доставки зависит от параметров, которые нужно получить из разных источников:

  • Откуда везут — адрес склада или ближайшего магазина
  • Куда везут — адрес покупателя, до двери или до пункта выдачи
  • Что везут — вес и габариты товаров в корзине
  • Какой способ — курьер, ПВЗ, постамат, Почта России
  • Когда нужно — стандарт или экспресс

Параметры товаров хранятся в базе данных интернет-магазина. Тарифы доставки — либо в собственных таблицах (для партнёрских договоров с фиксированными ценами), либо приходят в реальном времени через API службы доставки.

Собственные тарифные таблицы

Для простых случаев — когда есть договор с фиксированными ценами или доставка своими силами — тарифы хранятся локально:

CREATE TABLE shipping_zones (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    regions TEXT[], -- массив кодов регионов или городов
    base_price DECIMAL(10,2),
    price_per_kg DECIMAL(10,2),
    price_per_km DECIMAL(10,2),
    min_days INT,
    max_days INT
);

CREATE TABLE shipping_methods (
    id SERIAL PRIMARY KEY,
    zone_id INT REFERENCES shipping_zones(id),
    name VARCHAR(100),
    carrier VARCHAR(50),
    multiplier DECIMAL(4,2) DEFAULT 1.0, -- для экспресс-доставки
    free_from DECIMAL(10,2) -- сумма заказа, с которой доставка бесплатна
);
class LocalShippingCalculator
{
    public function calculate(Cart $cart, Address $destination): Collection
    {
        $zone = $this->zoneDetector->detect($destination->city);
        $weight = $cart->totalWeight(); // кг
        $orderTotal = $cart->total();

        return ShippingMethod::where('zone_id', $zone->id)->get()
            ->map(function (ShippingMethod $method) use ($weight, $orderTotal, $zone) {
                $cost = $zone->base_price + ($weight * $zone->price_per_kg);
                $cost *= $method->multiplier;

                // Бесплатная доставка от определённой суммы
                if ($method->free_from && $orderTotal >= $method->free_from) {
                    $cost = 0;
                }

                return [
                    'id'       => $method->id,
                    'name'     => $method->name,
                    'carrier'  => $method->carrier,
                    'cost'     => round($cost, 2),
                    'min_days' => $zone->min_days * $method->multiplier < 1 ? 1 : (int)($zone->min_days / $method->multiplier),
                    'max_days' => $zone->max_days,
                    'free'     => $cost === 0.0,
                ];
            });
    }
}

API-расчёт в реальном времени

Когда нужны актуальные тарифы службы доставки, запрос уходит в их API. Пример с СДЭК:

class CdekShippingCalculator
{
    private string $baseUrl = 'https://api.cdek.ru/v2';

    public function calculate(
        string $fromCity,
        string $toCity,
        float $weight,
        array $dimensions
    ): array {
        $token = $this->authenticate();

        $response = Http::withToken($token)
            ->post("{$this->baseUrl}/calculator/tarifflist", [
                'from_location' => ['city' => $fromCity],
                'to_location'   => ['city' => $toCity],
                'packages'      => [[
                    'weight' => (int)($weight * 1000), // граммы
                    'length' => $dimensions['length'],
                    'width'  => $dimensions['width'],
                    'height' => $dimensions['height'],
                ]],
            ]);

        return collect($response->json('tariff_codes'))
            ->map(fn($t) => [
                'tariff_code'  => $t['tariff_code'],
                'tariff_name'  => $t['tariff_name'],
                'cost'         => $t['delivery_sum'],
                'min_days'     => $t['period_min'],
                'max_days'     => $t['period_max'],
            ])
            ->toArray();
    }
}

Агрегация нескольких служб

Реальный калькулятор обычно показывает варианты от нескольких служб одновременно. Запросы уходят параллельно:

public function getShippingOptions(Cart $cart, Address $address): array
{
    $weight = $cart->totalWeight();
    $dimensions = $cart->boundingBox();

    // Параллельные запросы к службам доставки
    $results = collect([
        'cdek'     => fn() => $this->cdek->calculate($address, $weight, $dimensions),
        'boxberry' => fn() => $this->boxberry->calculate($address, $weight, $dimensions),
        'pochta'   => fn() => $this->russianPost->calculate($address, $weight, $dimensions),
    ])->map(function ($calculator, $carrier) {
        try {
            return $calculator();
        } catch (\Exception $e) {
            // Если один из сервисов недоступен — не ломаем всё
            logger()->warning("Shipping calculator error: $carrier", ['error' => $e->getMessage()]);
            return [];
        }
    })->flatten(1)->sortBy('cost')->values();

    return $results->toArray();
}

Если СДЭК вернул ошибку — показываем только Почту России и Boxberry. Покупатель не видит ошибку, видит меньше вариантов.

Кеширование расчётов

Запросы к API службы доставки — медленные (200–800 мс) и платные (некоторые шлюзы считают обращения). Кешировать стоит по ключу из параметров:

public function calculateCached(string $fromCity, string $toCity, float $weight): array
{
    $cacheKey = "shipping:{$fromCity}:{$toCity}:" . round($weight, 1);

    return Cache::remember($cacheKey, now()->addMinutes(30), function () use ($fromCity, $toCity, $weight) {
        return $this->calculate($fromCity, $toCity, $weight);
    });
}

Тарифы меняются редко — 30 минут вполне достаточно. При обновлении тарифов — инвалидация кеша по паттерну shipping:*.

Интерфейс калькулятора

На странице товара или корзины — компактный блок: поле ввода города или индекса, список методов с ценами и сроками. Без перезагрузки страницы:

const ShippingCalculator = () => {
  const [city, setCity] = useState('');
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);

  const calculate = useMemo(
    () =>
      debounce(async (cityValue) => {
        if (cityValue.length < 3) return;
        setLoading(true);
        try {
          const res = await fetch('/api/shipping/calculate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ city: cityValue, cart_id: cartId }),
          });
          const data = await res.json();
          setOptions(data.options);
        } finally {
          setLoading(false);
        }
      }, 600),
    [cartId]
  );

  return (
    <div className="shipping-calculator">
      <input
        value={city}
        onChange={(e) => { setCity(e.target.value); calculate(e.target.value); }}
        placeholder="Введите ваш город"
      />
      {loading && <Spinner />}
      {options.map((opt) => (
        <ShippingOption key={opt.id} option={opt} />
      ))}
    </div>
  );
};

Дебаунс на 600 мс — не стреляем запросами после каждого символа.

Объёмный вес

Многие службы считают оплачиваемый вес как максимум из фактического и объёмного:

public function billableWeight(float $actualKg, array $dimensions): float
{
    // Стандартный коэффициент: 1 кг = 5000 см³
    $volumetricWeight = ($dimensions['length'] * $dimensions['width'] * $dimensions['height']) / 5000;
    return max($actualKg, $volumetricWeight);
}

Для воздушной доставки коэффициент другой (6000 или 6800 см³/кг), для морской — ещё другой. Это нужно учитывать при расчёте, иначе расценки будут занижены.

Сроки разработки

Калькулятор с одной службой доставки по фиксированным тарифам — 2–3 дня. С реальным API одной службы — 3–5 дней (включая обработку ошибок и кеширование). Агрегатор на 3–5 служб с интерфейсом выбора — 2–3 недели.