Developing an Auction System for 1C-Bitrix
An auction on a website is a sales mechanism where the product's price is determined by buyer competition: the winner is the one who offered the highest amount within the allotted time. 1C-Bitrix does not contain auction logic — it must be developed on top of the standard catalog and sale modules.
Types of Auctions
Before development, it's important to define business requirements by auction type:
| Type | Description | Features |
|---|---|---|
| English (Open) | Bids rise, highest wins | Most common |
| Dutch (Reverse) | Price drops, first to agree wins | Needs price drop timer |
| Auto-bid | Maximum bid set, system bids automatically | Complex logic |
| "Buy Now" | Auction + fixed price for instant purchase | Parallel sales channel |
Data Structure
An auction lot is an extension of the product card. Storage via infoblock properties or separate tables.
Table b_local_auction:
| Field | Type | Description |
|---|---|---|
| ID | int | Primary key |
| PRODUCT_ID | int | Link to infoblock element |
| START_PRICE | decimal | Starting price |
| MIN_STEP | decimal | Minimum bid increment |
| CURRENT_PRICE | decimal | Current maximum bid |
| CURRENT_WINNER_ID | int | User ID with leading bid |
| RESERVE_PRICE | decimal | Reserve price (hidden) |
| BUY_NOW_PRICE | decimal | "Buy Now" price (optional) |
| START_TIME | datetime | Auction start |
| END_TIME | datetime | Auction end |
| STATUS | enum | PENDING, ACTIVE, ENDED, CANCELLED |
| BIDS_COUNT | int | Number of bids |
Table b_local_auction_bid — bid history:
| Field | Description |
|---|---|
| AUCTION_ID | Link to auction |
| USER_ID | Participant |
| AMOUNT | Bid amount |
| AUTO_MAX | Maximum for auto-bid |
| CREATED_AT | Bid time |
| IP | IP address (for anti-fraud) |
Bid Acceptance Logic
Accepting a new bid is a critical section: multiple users can place bids simultaneously. Without locks, data races occur.
function placeBid(int $auctionId, int $userId, float $amount): BidResult
{
// Start transaction with row locking
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// SELECT FOR UPDATE — lock the row
$auction = $connection->query(
"SELECT * FROM b_local_auction WHERE ID = {$auctionId} FOR UPDATE"
)->fetch();
// Validations
if ($auction['STATUS'] !== 'ACTIVE') {
throw new \Exception('Auction is not active');
}
if (new DateTime() > new DateTime($auction['END_TIME'])) {
throw new \Exception('Auction is finished');
}
if ($amount < $auction['CURRENT_PRICE'] + $auction['MIN_STEP']) {
throw new \Exception('Bid below minimum: ' . ($auction['CURRENT_PRICE'] + $auction['MIN_STEP']));
}
if ($auction['CURRENT_WINNER_ID'] === $userId) {
throw new \Exception('You are already leading');
}
// Record the bid
AuctionBidTable::add([
'AUCTION_ID' => $auctionId,
'USER_ID' => $userId,
'AMOUNT' => $amount,
'CREATED_AT' => new DateTime(),
]);
// Update current bid
AuctionTable::update($auctionId, [
'CURRENT_PRICE' => $amount,
'CURRENT_WINNER_ID' => $userId,
'BIDS_COUNT' => $auction['BIDS_COUNT'] + 1,
]);
// Extend time if bid in last minutes
if ((new DateTime($auction['END_TIME']))->getTimestamp() - time() < 300) {
AuctionTable::update($auctionId, [
'END_TIME' => (new DateTime())->modify('+5 minutes'),
]);
}
$connection->commitTransaction();
// Notify previous leader
notifyOutbid($auction['CURRENT_WINNER_ID'], $auctionId, $amount);
} catch (\Exception $e) {
$connection->rollbackTransaction();
throw $e;
}
}
Real-time Updates
An auction requires displaying the current bid without page reload. Options:
Polling — simplest: JavaScript makes AJAX request every 5-10 seconds.
setInterval(() => {
fetch('/local/ajax/auction_state.php?id=' + auctionId)
.then(r => r.json())
.then(data => updateUI(data));
}, 5000);
Server-Sent Events (SSE) — server sends events when it changes. Less load than polling.
WebSocket — maximum real-time, but requires separate server (Node.js, Ratchet). May be unavailable on Bitrix hosting.
For most auctions, polling with 5-10 second intervals is sufficient.
Auction Completion and Order Creation
An agent checks auctions with expired 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']) {
// Notify winner
notifyWinner($auction['CURRENT_WINNER_ID'], $auction);
// Create order or send payment link
createAuctionOrder($auction);
} else {
// Reserve price not met
notifyReserveNotMet($auction);
}
}
Anti-fraud and Rules
- One user cannot place bids from multiple accounts — check by IP + cookies + account history.
- Bids only from authorized users.
- Optional: phone verification or prepayment (amount reservation) for auction access.
- Minimum account: registered N days ago, made at least one purchase.
Development Timeline
| Option | Scope | Timeline |
|---|---|---|
| Basic Auction | English, bids, timer, polling | 8-10 days |
| Extended | Auto-bid, "buy now", notifications | 12-16 days |
| Full Platform | Multiple types, WebSocket, analytics, anti-fraud | 20-30 days |







