Pre-order Module Development for 1C-Bitrix
A pre-order is the ability to pay for a product before it goes on sale: a new item not yet in stock, or a limited edition with a locked release date. Unlike "notify me when available", a pre-order involves a real payment or fund reservation. The standard b_sale_order does not support the state "product unavailable but pre-order accepted" — there is no deferred fulfillment status, no deadline, no mass activation.
Data Model
Module vendor.preorder:
-
b_vendor_preorder_campaign— pre-order campaigns: id, iblock_element_id (product), sku_id, name, available_from (sale start date), preorder_from, preorder_to, limit_qty (pre-order limit), reserved_qty, payment_type (full/deposit), deposit_amount, deposit_percent, status (draft/active/closed/fulfilled) -
b_vendor_preorder_order— pre-orders: id, campaign_id, user_id, order_id (reference to b_sale_order), qty, paid_amount, status (pending/paid/confirmed/shipped/cancelled), created_at -
b_vendor_preorder_notify_queue— notification queue on activation
Pre-order Campaign
A campaign is linked to a specific product or SKU. Parameters:
- Payment type: full prepayment or deposit (fixed amount or percentage of price)
-
Limit: maximum number of pre-orders —
limit_qty. When the limit is reached the form closes -
Period:
preorder_from/preorder_to— when pre-orders are accepted -
Delivery date:
available_from— when the product actually arrives, shown to the buyer
Creating a Pre-order
class PreorderService
{
public function create(int $campaignId, int $userId, int $qty): PreorderResult
{
$campaign = CampaignTable::getById($campaignId)->fetch();
if ($campaign['STATUS'] !== 'active') {
return PreorderResult::error('Campaign is not active');
}
if ($campaign['LIMIT_QTY'] && ($campaign['RESERVED_QTY'] + $qty) > $campaign['LIMIT_QTY']) {
return PreorderResult::error('Pre-order limit reached');
}
// Determine the amount to pay
$productPrice = $this->getProductPrice($campaign['SKU_ID']);
$payAmount = $campaign['PAYMENT_TYPE'] === 'deposit'
? ($campaign['DEPOSIT_AMOUNT'] ?: $productPrice * $campaign['DEPOSIT_PERCENT'] / 100)
: $productPrice * $qty;
// Create an order in sale with a custom status
$order = \Bitrix\Sale\Order::create(SITE_ID, $userId);
$order->setField('STATUS_ID', 'PRE'); // custom status "Pre-order"
// ...add product to cart...
$order->save();
PreorderOrderTable::add([
'CAMPAIGN_ID' => $campaignId,
'USER_ID' => $userId,
'ORDER_ID' => $order->getId(),
'QTY' => $qty,
'PAID_AMOUNT' => $payAmount,
'STATUS' => 'pending',
]);
// Atomically increment the reservation counter
CampaignTable::incrementReserved($campaignId, $qty);
return PreorderResult::success($order->getId());
}
}
Activating Pre-orders When Stock Arrives
When the product arrives in stock, the manager sets the campaign to fulfilled. An agent processes pre-orders in bulk:
public static function activateCampaign(int $campaignId): void
{
$preorders = PreorderOrderTable::getList([
'filter' => ['CAMPAIGN_ID' => $campaignId, 'STATUS' => 'paid'],
'order' => ['CREATED_AT' => 'ASC'], // confirm earliest orders first
])->fetchAll();
foreach ($preorders as $preorder) {
// Change the main order status to standard "Processing"
$order = \Bitrix\Sale\Order::load($preorder['ORDER_ID']);
$order->setField('STATUS_ID', 'N');
$order->save();
PreorderOrderTable::update($preorder['ID'], ['STATUS' => 'confirmed']);
// Queue a notification for the buyer
NotifyQueueTable::add(['PREORDER_ID' => $preorder['ID']]);
}
}
Display on the Product Card
Component vendor:preorder.button:
- If the campaign is active: shows the pre-order form with price, delivery date, and remaining slots
- If the limit is reached: "Pre-order closed" with a button to subscribe to a standard notification
- Countdown to sale start date (JavaScript)
- Limit fill progress bar: "47 of 100 reserved"
Admin Interface
- Creating and managing campaigns
- List of pre-orders per campaign with filter by payment status
- "Activate campaign" button — triggers mass confirmation
- Export pre-order list to CSV for logistics
- Statistics: conversion to payment, average pre-order value
Development Timeline
| Stage | Duration |
|---|---|
| ORM tables, campaign model | 1 day |
| Pre-order creation service, atomic reservation | 2 days |
| Integration with b_sale_order, custom status | 1 day |
| Campaign activation, mass confirmation agent | 2 days |
| Product card widget | 1 day |
| Admin interface | 2 days |
| Testing | 1 day |
Total: 10 working days. For a deposit with balance payment on activation — additional payment system integration: +2 days.







