Разработка системы персональных цен для B2B интернет-магазина

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка системы персональных цен для B2B интернет-магазина
Сложная
~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

Разработка системы персональных цен для B2B интернет-магазина

B2B-магазин принципиально отличается от B2C: каждый контрагент работает по индивидуальным условиям — своей ценовой группе, персональным скидкам на SKU, договорным ценам, отсрочкам платежа. Публичного прайса может не существовать вовсе. Разработка полноценной B2B-ценовой системы — одна из наиболее трудоёмких задач в e-commerce: от 10 до 20 рабочих дней в зависимости от глубины интеграции с ERP.

Архитектура ценообразования

Система строится вокруг нескольких концепций:

  • Price list (прайс-лист) — набор цен для группы клиентов
  • Customer group — сегмент: розница, оптовик, дилер, VIP
  • Contract price — индивидуальная цена для конкретного контрагента на конкретный SKU
  • Volume tier — ступенчатая скидка за объём
  • Customer discount — персональная скидка в процентах поверх прайс-листа

Приоритет применения цен (от высшего к низшему):

  1. Contract price (персональная договорная цена на SKU)
  2. Customer group price list
  3. Volume tier из прайс-листа
  4. Base retail price

Схема базы данных

-- Прайс-листы
CREATE TABLE price_lists (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255),
    currency VARCHAR(3) DEFAULT 'RUB',
    is_default BOOLEAN DEFAULT FALSE
);

-- Цены в прайс-листе
CREATE TABLE price_list_items (
    id BIGSERIAL PRIMARY KEY,
    price_list_id BIGINT REFERENCES price_lists(id) ON DELETE CASCADE,
    product_id BIGINT NOT NULL,
    variant_id BIGINT,
    price NUMERIC(14,4) NOT NULL,
    min_qty INT DEFAULT 1
);
CREATE UNIQUE INDEX idx_pli_product_qty
    ON price_list_items(price_list_id, product_id, COALESCE(variant_id, 0), min_qty);

-- Привязка групп к прайс-листу
CREATE TABLE customer_groups (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255),
    price_list_id BIGINT REFERENCES price_lists(id),
    discount_percent NUMERIC(5,2) DEFAULT 0
);

-- Персональные цены контрагентов
CREATE TABLE customer_prices (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
    product_id BIGINT NOT NULL,
    variant_id BIGINT,
    price NUMERIC(14,4) NOT NULL,
    min_qty INT DEFAULT 1,
    valid_from DATE,
    valid_to DATE
);

Резолвер цены

Ключевой класс, вычисляющий итоговую цену для конкретного пользователя:

class B2BPriceResolver
{
    public function resolve(int $productId, int $qty, User $customer): PriceResult
    {
        // 1. Персональная договорная цена
        $contractPrice = CustomerPrice::where('user_id', $customer->id)
            ->where('product_id', $productId)
            ->where('min_qty', '<=', $qty)
            ->where(fn($q) => $q->whereNull('valid_from')->orWhere('valid_from', '<=', today()))
            ->where(fn($q) => $q->whereNull('valid_to')->orWhere('valid_to', '>=', today()))
            ->orderByDesc('min_qty')
            ->first();

        if ($contractPrice) {
            return new PriceResult($contractPrice->price, 'contract');
        }

        // 2. Прайс-лист группы с volume tier
        $group = $customer->customerGroup;
        if ($group?->priceList) {
            $tier = PriceListItem::where('price_list_id', $group->price_list_id)
                ->where('product_id', $productId)
                ->where('min_qty', '<=', $qty)
                ->orderByDesc('min_qty')
                ->first();

            if ($tier) {
                $price = $tier->price;
                if ($group->discount_percent > 0) {
                    $price *= (1 - $group->discount_percent / 100);
                }
                return new PriceResult(round($price, 4), 'price_list');
            }
        }

        // 3. Базовая цена
        $product = Product::find($productId);
        return new PriceResult($product->price, 'retail');
    }
}

Кеширование цен

Резолвер вызывается при каждом отображении каталога — для 100 товаров на странице это 100 вызовов. Без кеша это катастрофа производительности.

Стратегия: кешировать персональный прайс-лист пользователя целиком при логине, инвалидировать при изменении его ценовых условий:

// Кеш персонального прайса
$cacheKey = "b2b_prices:{$user->id}";
$prices = Cache::remember($cacheKey, 3600, function () use ($user) {
    return $this->buildUserPriceMap($user); // все цены для всех SKU
});

// Инвалидация при обновлении условий
Cache::forget("b2b_prices:{$user->id}");

Для крупных каталогов (50k+ SKU) прайс строится в фоновой задаче и сохраняется в Redis Hash.

Отображение цен в каталоге

B2B-пользователь видит только свои цены, без публичного прайса. Неавторизованный пользователь видит заглушку «Войдите для просмотра цен» или запрос на регистрацию. Это реализуется middleware:

// React: условный рендер цены
const PriceDisplay = ({ product }: { product: Product }) => {
  const { user } = useAuth();

  if (!user) return <RequestAccessButton />;
  if (!product.b2b_price) return <PriceOnRequest />;

  return (
    <div>
      <span className="text-lg font-bold">{formatPrice(product.b2b_price)}</span>
      {product.b2b_source === 'contract' && (
        <span className="text-xs text-green-600 ml-2">Договорная цена</span>
      )}
    </div>
  );
};

Управление ценами в admin-панели

Admin-панель обеспечивает:

  • Создание и редактирование прайс-листов с массовым импортом через CSV/XLSX
  • Назначение клиентов на группы
  • Установку персональных цен на SKU с датами действия
  • Предпросмотр цены: «Какую цену видит клиент X на товар Y?»
  • Журнал изменений цен с указанием автора и времени

Импорт из ERP

Для синхронизации с 1С, SAP или другой ERP реализуется API-приёмник или file-watcher:

// Команда импорта прайса из CSV
Artisan::call('prices:import', [
    '--file'       => '/var/imports/prices_20240115.csv',
    '--price-list' => 3,
    '--mode'       => 'upsert', // или 'replace'
]);

Формат CSV: sku,price,min_qty,valid_from,valid_to. Импорт через очередь, результат — отчёт с количеством добавленных/обновлённых/пропущенных позиций.

Мультивалютность

Если B2B работает с несколькими валютами, прайс-листы хранятся в валюте контракта. Конвертация в валюту отображения по курсу ЦБ (кешируется на 1 час):

$displayPrice = $price * ExchangeRateService::getRate($priceList->currency, 'RUB');

Курс фиксируется в момент создания заказа — изменение курса между просмотром каталога и оплатой должно быть явно обозначено на checkout.

Согласование цен (price negotiation)

Для enterprise-сегмента реализуем flow запроса цены: клиент запрашивает персональное предложение, менеджер устанавливает договорную цену, клиент видит её в своём каталоге. Статусы: requested → under_review → approved → active. Уведомления по email на каждом переходе.