Реалізація синхронізації цін з дропшипінг-постачальником

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація синхронізації цін з дропшипінг-постачальником
Середня
~2-3 робочих дні
Часті питання

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

Етапи розробки

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

  • 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

Реалізація синхронізації цін з дропшиппінг-постачальником

Ціни постачальника змінюються за розписанням (новий прайс раз на тиждень) або в реальному часі (товари залежні від курсу, динамічне ціноутворення). В обох випадках магазин не може працювати з застарілим прайсом — либо теряє маржу при зростанні закупочної ціни, либо завищує ціни при зниженні, програючи конкурентам.

Джерела цін

  • REST API — ендпоінт з актуальними цінами, можна запитувати часто
  • FTP/CSV прайс-лист — файл оновлюється раз на день або рідше
  • Webhook від постачальника — push при кожній зміні ціни
  • Парсинг сайту постачальника — крайній варіант при відсутності API (вимагає обслуговування)

Зберігання ціноової історії

Schema::create('dropship_price_log', function (Blueprint $table) {
    $table->id();
    $table->foreignId('dropship_product_id')->constrained();
    $table->decimal('prev_supplier_price', 10, 2);
    $table->decimal('new_supplier_price', 10, 2);
    $table->decimal('prev_retail_price', 10, 2)->nullable();
    $table->decimal('new_retail_price', 10, 2)->nullable();
    $table->decimal('margin_percent', 5, 2)->nullable();
    $table->string('source')->default('sync');
    $table->timestamp('recorded_at');

    $table->index(['dropship_product_id', 'recorded_at']);
});

Калькулятор розничної ціни

Розничная ціна розраховується з закупочної з застосуванням правил маржі. Правила можуть бути глобальними, на рівні постачальника, категорії або конкретного товару.

class PriceCalculator
{
    public function calculate(DropshipProduct $dp): float
    {
        $supplierPrice = $dp->supplier_price;
        $marginRule    = $this->resolveMarginRule($dp);

        return match($marginRule->type) {
            'percent' => round($supplierPrice * (1 + $marginRule->value / 100), 2),
            'fixed'   => round($supplierPrice + $marginRule->value, 2),
            'markup_table' => $this->applyMarkupTable($supplierPrice, $marginRule->table),
        };
    }

    private function resolveMarginRule(DropshipProduct $dp): MarginRule
    {
        // Приоритет: товар > категорія > постачальник > глобальні
        return $dp->margin_rule
            ?? $dp->product?->category?->margin_rule
            ?? $dp->supplier->margin_rule
            ?? MarginRule::getDefault();
    }

    /**
     * Ступенчата наценка: дорогі товари — менший %
     * [до 1000 → +40%, 1000–5000 → +25%, 5000+ → +15%]
     */
    private function applyMarkupTable(float $price, array $table): float
    {
        foreach ($table as $tier) {
            if ($price <= $tier['max_price']) {
                return round($price * (1 + $tier['percent'] / 100), 2);
            }
        }

        // Останній рівень без обмеження максимуму
        $last = end($table);
        return round($price * (1 + $last['percent'] / 100), 2);
    }
}

Job синхронізації цін

class SyncSupplierPricesJob implements ShouldQueue
{
    public $tries = 3;
    public $backoff = [60, 300, 900];

    public function handle(
        SupplierConnectorFactory $factory,
        PriceCalculator $calculator,
    ): void {
        $connector  = $factory->make($this->supplier);
        $priceList  = $connector->getPriceList(); // масив [sku => price]
        $updatedCount = 0;

        foreach ($priceList as $sku => $newSupplierPrice) {
            $dp = DropshipProduct::where([
                'supplier_id'  => $this->supplier->id,
                'supplier_sku' => $sku,
            ])->first();

            if (!$dp) continue;

            // Пропускаємо, якщо ціна не змінилась
            if (abs($dp->supplier_price - $newSupplierPrice) < 0.01) continue;

            $prevSupplierPrice = $dp->supplier_price;
            $prevRetailPrice   = $dp->product?->price;

            $dp->update(['supplier_price' => $newSupplierPrice]);

            // Пересчитуємо розничну ціну, якщо немає ручної фіксації
            $newRetailPrice = null;
            if ($dp->product && !$dp->product->price_locked) {
                $newRetailPrice = $calculator->calculate($dp);
                $dp->product->update(['price' => $newRetailPrice]);
            }

            DropshipPriceLog::create([
                'dropship_product_id' => $dp->id,
                'prev_supplier_price' => $prevSupplierPrice,
                'new_supplier_price'  => $newSupplierPrice,
                'prev_retail_price'   => $prevRetailPrice,
                'new_retail_price'    => $newRetailPrice,
                'source'              => 'sync',
                'recorded_at'         => now(),
            ]);

            $updatedCount++;
        }

        Log::info('Price sync completed', [
            'supplier' => $this->supplier->slug,
            'updated'  => $updatedCount,
        ]);
    }
}

Захист від різких стрибків ціни

Іноді в прайс-листі постачальника з'являються помилкові ціни (нуль, дуже високе значення, опечатка). Без захисту магазин виставить некоректну розничну ціну:

class PriceSanityChecker
{
    private const MAX_CHANGE_PERCENT = 50; // не оновлюємо, якщо зміна > 50%

    public function isSafe(float $prevPrice, float $newPrice): bool
    {
        if ($newPrice <= 0) return false;

        if ($prevPrice <= 0) return true; // перша ціна — приймаємо будь-яку позитивну

        $changePercent = abs($newPrice - $prevPrice) / $prevPrice * 100;

        if ($changePercent > self::MAX_CHANGE_PERCENT) {
            // Логуємо для ручної перевірки
            Log::warning('Suspicious price change detected', [
                'prev'    => $prevPrice,
                'new'     => $newPrice,
                'change%' => round($changePercent, 1),
            ]);
            return false;
        }

        return true;
    }
}

Товари з підозрілою зміною ціни помішаються в чергу ручної перевірки — менеджер бачить їх в окремому розділі адмініці.

Валюта та курсові пересчити

Якщо постачальник виставляє ціни в USD або EUR, а магазин працює в RUB:

class CurrencyPriceConverter
{
    public function convert(float $price, string $fromCurrency, string $toCurrency): float
    {
        if ($fromCurrency === $toCurrency) return $price;

        $rate = Cache::remember(
            "exchange_rate_{$fromCurrency}_{$toCurrency}",
            3600, // кеш на 1 годину
            fn() => $this->fetchRate($fromCurrency, $toCurrency)
        );

        return round($price * $rate, 2);
    }

    private function fetchRate(string $from, string $to): float
    {
        // ЦБ РФ: cbr.ru/scripts/XML_daily.asp
        // Або openexchangerates.org, fixer.io
        $response = Http::get('https://api.exchangerate-api.com/v4/latest/' . $from);
        return $response->json("rates.{$to}");
    }
}

Розписання

// Синхронізація цін кожні 6 годин
$schedule->job(SyncAllSupplierPricesJob::class)->everySixHours()->withoutOverlapping();

Терміни

Синхронізація цін з одним постачальником + калькулятор маржі — 3–4 робочих дні. Ступенчата наценка, курсові пересчити, захист від стрибків — ще 1–2 дні.