Price synchronization with dropshipping supplier

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Price synchronization with dropshipping supplier
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Implementation of Price Synchronization with Dropshipping Supplier

Supplier prices change on schedule (new price list once a week) or in realtime (exchange-dependent goods, dynamic pricing). In both cases, store cannot work with outdated prices — either loses margin when supplier price rises, or charges excessive prices when it falls, losing to competitors.

Price Sources

  • REST API — endpoint with current prices, can query frequently
  • FTP/CSV price list — file updated once a day or less
  • Webhook from supplier — push on every price change
  • Website parsing — last resort without API (requires maintenance)

Storing Price History

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']);
});

Retail Price Calculator

Retail price is calculated from wholesale with margin rules applied. Rules can be global, supplier-level, category, or product-specific.

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
    {
        // Priority: product > category > supplier > global
        return $dp->margin_rule
            ?? $dp->product?->category?->margin_rule
            ?? $dp->supplier->margin_rule
            ?? MarginRule::getDefault();
    }

    /**
     * Tiered markup: expensive items — lower %
     * [up to 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 level without max limit
        $last = end($table);
        return round($price * (1 + $last['percent'] / 100), 2);
    }
}

Price Sync 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(); // array [sku => price]
        $updatedCount = 0;

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

            if (!$dp) continue;

            // Skip if price unchanged
            if (abs($dp->supplier_price - $newSupplierPrice) < 0.01) continue;

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

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

            // Recalculate retail price if not manually locked
            $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,
        ]);
    }
}

Protection from Drastic Price Changes

Sometimes supplier price list has errors (zero, very high, typo). Without protection, store shows incorrect retail price:

class PriceSanityChecker
{
    private const MAX_CHANGE_PERCENT = 50; // don't update if change > 50%

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

        if ($prevPrice <= 0) return true; // first price — accept any positive

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

        if ($changePercent > self::MAX_CHANGE_PERCENT) {
            // Log for manual review
            Log::warning('Suspicious price change detected', [
                'prev'    => $prevPrice,
                'new'     => $newPrice,
                'change%' => round($changePercent, 1),
            ]);
            return false;
        }

        return true;
    }
}

Products with suspicious price changes go to manual review queue — manager sees them in separate admin section.

Currency and Exchange Rate Conversions

If supplier sets prices in USD or EUR, but store works in 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, // cache 1 hour
            fn() => $this->fetchRate($fromCurrency, $toCurrency)
        );

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

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

Schedule

// Price sync every 6 hours
$schedule->job(SyncAllSupplierPricesJob::class)->everySixHours()->withoutOverlapping();

Timeline

Price sync with one supplier + margin calculator — 3–4 business days. Tiered markup, exchange rate conversion, change protection — another 1–2 days.