Разработка системы аукциона на 1С-Битрикс
Аукцион на сайте — это механизм продаж, где цена товара определяется конкуренцией покупателей: побеждает тот, кто в отведённое время предложил наибольшую сумму. 1С-Битрикс не содержит аукционной логики — её нужно разрабатывать поверх стандартных модулей catalog и sale.
Типы аукционов
Перед разработкой важно зафиксировать бизнес-требования по типу аукциона:
| Тип | Описание | Особенности |
|---|---|---|
| Английский (открытый) | Ставки растут, побеждает максимальная | Самый распространённый |
| Нидерландский (обратный) | Цена снижается, побеждает первый согласившийся | Нужен таймер снижения цены |
| Авто-ставка | Указывается максимум, система торгуется автоматически | Сложная логика |
| «Купи сейчас» | Аукцион + фиксированная цена для мгновенной покупки | Параллельный канал продажи |
Структура данных
Аукционный лот — расширение товарной карточки. Хранение через свойства инфоблока или отдельные таблицы.
Таблица b_local_auction:
| Поле | Тип | Описание |
|---|---|---|
| ID | int | Первичный ключ |
| PRODUCT_ID | int | Ссылка на элемент инфоблока |
| START_PRICE | decimal | Стартовая цена |
| MIN_STEP | decimal | Минимальный шаг ставки |
| CURRENT_PRICE | decimal | Текущая максимальная ставка |
| CURRENT_WINNER_ID | int | ID пользователя с ведущей ставкой |
| RESERVE_PRICE | decimal | Резервная цена (скрытая) |
| BUY_NOW_PRICE | decimal | Цена «Купить сейчас» (опционально) |
| START_TIME | datetime | Начало аукциона |
| END_TIME | datetime | Конец аукциона |
| STATUS | enum | PENDING, ACTIVE, ENDED, CANCELLED |
| BIDS_COUNT | int | Количество ставок |
Таблица b_local_auction_bid — история ставок:
| Поле | Описание |
|---|---|
| AUCTION_ID | Ссылка на аукцион |
| USER_ID | Участник |
| AMOUNT | Сумма ставки |
| AUTO_MAX | Максимум для авто-ставки |
| CREATED_AT | Время ставки |
| IP | IP-адрес (для антифрода) |
Логика принятия ставки
Принятие новой ставки — критическая секция: несколько пользователей могут делать ставки одновременно. Без блокировок возникнут гонки данных.
function placeBid(int $auctionId, int $userId, float $amount): BidResult
{
// Начало транзакции с блокировкой строки аукциона
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// SELECT FOR UPDATE — блокируем строку
$auction = $connection->query(
"SELECT * FROM b_local_auction WHERE ID = {$auctionId} FOR UPDATE"
)->fetch();
// Валидации
if ($auction['STATUS'] !== 'ACTIVE') {
throw new \Exception('Аукцион не активен');
}
if (new DateTime() > new DateTime($auction['END_TIME'])) {
throw new \Exception('Аукцион завершён');
}
if ($amount < $auction['CURRENT_PRICE'] + $auction['MIN_STEP']) {
throw new \Exception('Ставка ниже минимальной: ' . ($auction['CURRENT_PRICE'] + $auction['MIN_STEP']));
}
if ($auction['CURRENT_WINNER_ID'] === $userId) {
throw new \Exception('Вы уже лидируете');
}
// Записать ставку
AuctionBidTable::add([
'AUCTION_ID' => $auctionId,
'USER_ID' => $userId,
'AMOUNT' => $amount,
'CREATED_AT' => new DateTime(),
]);
// Обновить текущую ставку
AuctionTable::update($auctionId, [
'CURRENT_PRICE' => $amount,
'CURRENT_WINNER_ID' => $userId,
'BIDS_COUNT' => $auction['BIDS_COUNT'] + 1,
]);
// Продление времени при ставке в последние минуты
if ((new DateTime($auction['END_TIME']))->getTimestamp() - time() < 300) {
AuctionTable::update($auctionId, [
'END_TIME' => (new DateTime())->modify('+5 minutes'),
]);
}
$connection->commitTransaction();
// Уведомить предыдущего лидера
notifyOutbid($auction['CURRENT_WINNER_ID'], $auctionId, $amount);
} catch (\Exception $e) {
$connection->rollbackTransaction();
throw $e;
}
}
Real-time обновления
Аукцион требует отображения текущей ставки без перезагрузки страницы. Варианты:
Polling — самый простой: JavaScript делает AJAX-запрос каждые 5-10 секунд.
setInterval(() => {
fetch('/local/ajax/auction_state.php?id=' + auctionId)
.then(r => r.json())
.then(data => updateUI(data));
}, 5000);
Server-Sent Events (SSE) — сервер сам отправляет события при изменении. Меньше нагрузки, чем polling.
WebSocket — максимально real-time, но требует отдельного сервера (Node.js, Ratchet). На Битрикс-хостинге может быть недоступно.
Для большинства аукционов polling с интервалом 5-10 секунд — достаточное решение.
Завершение аукциона и создание заказа
Агент проверяет аукционы, у которых истекло END_TIME:
$ended = AuctionTable::getList([
'filter' => ['STATUS' => 'ACTIVE', '<END_TIME' => new DateTime()]
])->fetchAll();
foreach ($ended as $auction) {
AuctionTable::update($auction['ID'], ['STATUS' => 'ENDED']);
if ($auction['CURRENT_WINNER_ID'] && $auction['CURRENT_PRICE'] >= $auction['RESERVE_PRICE']) {
// Уведомить победителя
notifyWinner($auction['CURRENT_WINNER_ID'], $auction);
// Создать заказ или отправить ссылку на оплату
createAuctionOrder($auction);
} else {
// Резервная цена не достигнута
notifyReserveNotMet($auction);
}
}
Антифрод и правила
- Один пользователь не может делать ставки от нескольких аккаунтов — проверка по IP + cookies + истории аккаунта.
- Ставки только от авторизованных пользователей.
- Опционально: верификация телефона или предоплата (блокировка суммы) для доступа к аукциону.
- Минимальный аккаунт: зарегистрирован N дней назад, сделал хотя бы один заказ.
Сроки разработки
| Вариант | Состав | Срок |
|---|---|---|
| Базовый аукцион | Английский, ставки, таймер, polling | 8-10 дней |
| Расширенный | Авто-ставка, «купить сейчас», уведомления | 12-16 дней |
| Полная платформа | Несколько типов, WebSocket, аналитика, антифрод | 20-30 дней |







