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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка калькулятора доставки для інтернет-магазину
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

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

Покупець не повинен додавати товар у кошик, вводити адресу, натискати «Оформити» і лише на останньому кроці дізнаватися, що доставка коштує половину замовлення. Це одна з головних причин брошених кошиків. Калькулятор доставки вирішує цю проблему — показує вартість відразу, до початку оформлення.

Що розраховує калькулятор

Вартість доставки залежить від параметрів, які потрібно отримати з різних джерел:

  • Звідки везуть — адреса складу або найближчого магазину
  • Куди везуть — адреса покупця, до двері або до пункту видачі
  • Що везуть — вага і габарити товарів у кошику
  • Який спосіб — кур'єр, ПВЗ, постамат, Пошта
  • Коли потрібно — стандарт або експрес

Параметри товарів зберігаються в базі даних інтернет-магазину. Тарифи доставки — або у власних таблицях (для партнерських договорів з фіксованими цінами), або приходять в реальному часі через 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. Приклад з CDEK:

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();
}

Якщо CDEK повернув помилку — показуємо тільки Пошту та 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 тижні.