Setting Up Order Confirmation via SMS Code in 1C-Bitrix
Large stores with a high average order value sometimes have a requirement: confirm the buyer's intent via SMS before finalizing the order. This reduces the number of "accidental" orders and verifies the phone number, which is then used in a loyalty program. The standard 1C-Bitrix checkout does not have this mechanism — the order process must be intercepted.
Confirmation Architecture
Flow: the customer fills out the order form → clicks "Place Order" → the system sends an SMS with a code to the specified phone number → the customer enters the code → the order is created. The order in 1C-Bitrix is created only after successful code verification, not before.
Code storage — in the local_order_confirmations table:
CREATE TABLE local_order_confirmations (
ID INT AUTO_INCREMENT PRIMARY KEY,
PHONE VARCHAR(20) NOT NULL,
CODE VARCHAR(6) NOT NULL,
SESSION_ID VARCHAR(128),
ATTEMPTS TINYINT DEFAULT 0,
CONFIRMED CHAR(1) DEFAULT 'N',
CREATED_AT DATETIME NOT NULL,
EXPIRES_AT DATETIME NOT NULL,
INDEX idx_phone_code (PHONE, CODE),
INDEX idx_session (SESSION_ID)
);
Code lifetime — 5 minutes. Maximum attempts — 3. After exhaustion — a new code request with a delay (rate limit).
Integration into Checkout
If sale.order.ajax is used — intercept its JavaScript. Capture the form submission event:
// In result_modifier.php of the component or an included JS file
BX.addCustomEvent('onSaleComponentOrderSuccess', function(order) {
// Standard handling — disabled
});
document.querySelector('.order-confirm-btn').addEventListener('click', async (e) => {
e.preventDefault();
const phone = document.querySelector('[name="ORDER_PROP_PHONE"]').value;
// Request SMS
const res = await fetch('/local/api/order-confirm/send', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Bitrix-Csrf-Token': BX.bitrix_sessid()},
body: JSON.stringify({phone})
});
if (res.ok) {
showCodeInputModal(phone);
}
});
After successful code confirmation, the frontend sends a confirmation token along with the order data; the server validates the token before creating the order.
Server Side
class OrderConfirmController
{
public function sendCode(): void
{
$phone = $this->normalizePhone($_POST['phone'] ?? '');
if (!$phone) { $this->error('Invalid phone number'); }
// Rate limit: no more than 3 sends in 15 minutes
if ($this->isRateLimited($phone)) {
$this->error('Too many requests. Please wait 15 minutes.');
}
$code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
$expiresAt = (new \DateTime())->modify('+5 minutes');
OrderConfirmationTable::add([
'PHONE' => $phone,
'CODE' => password_hash($code, PASSWORD_DEFAULT), // do not store plain code
'SESSION_ID' => session_id(),
'EXPIRES_AT' => \Bitrix\Main\Type\DateTime::createFromTimestamp($expiresAt->getTimestamp()),
]);
SmsService::send($phone, "Your order confirmation code: {$code}. Valid for 5 minutes.");
$this->success(['expires_in' => 300]);
}
public function verifyCode(): void
{
$phone = $this->normalizePhone($_POST['phone'] ?? '');
$code = $_POST['code'] ?? '';
$record = OrderConfirmationTable::getActiveRecord($phone, session_id());
if (!$record) {
$this->error('Code not found or expired');
}
// Increment attempts
OrderConfirmationTable::incrementAttempts($record['ID']);
if ($record['ATTEMPTS'] >= 3) {
$this->error('Maximum attempts exceeded. Please request a new code.');
}
if (!password_verify($code, $record['CODE'])) {
$this->error('Invalid code');
}
// Code is valid — issue a one-time token for order creation
$token = bin2hex(random_bytes(32));
OrderConfirmationTable::markConfirmed($record['ID'], $token);
$this->success(['token' => $token]);
}
}
The code is stored in the database in hashed form — password_hash(). Even if the database is compromised, the codes are not exposed.
Token Validation on Order Creation
Before the standard order creation via OnBeforeSaleOrderSaved or controller override:
AddEventHandler('sale', 'OnBeforeSaleOrderSaved', function(\Bitrix\Sale\Order $order, array $data) {
if (!ConfirmConfig::isRequiredForOrder($order)) {
return; // not all orders require confirmation
}
$token = $_POST['confirm_token'] ?? '';
if (!OrderConfirmationTable::isValidToken($token, session_id())) {
throw new \Exception('Order not confirmed via SMS');
}
});
SMS Provider
Integration with the provider via abstraction:
interface SmsProviderInterface {
public function send(string $phone, string $message): bool;
}
Implementations: SMS.ru, SMSC.ru, MTS Communicator, integration via Bitrix24 SMS. Switching the provider without changing business logic.
Scope of Work
- Confirmation table, rate limit and attempts logic
- API controller: send code, verify, issue token
- Checkout integration (JS interception, token passing)
- Token validation before order creation
- SMS provider connection
Timelines: 5–7 days with one SMS provider. 1.5–2 weeks with a custom checkout.







