Phone Verification at Checkout Configuration in 1C-Bitrix
OTP phone verification at checkout serves two purposes: it confirms the buyer actually exists, and it filters out fraudulent orders with fake phone numbers. Unverified numbers appear in 15–20% of fraudulent orders. In 1C-Bitrix, OTP is implemented via the order creation event and an SMS gateway.
Verification flow
- The user enters a phone number on the order page
- AJAX request — generate OTP, save to session/cache, send SMS
- The user enters the code
- AJAX request — verify the code, set the "phone confirmed" flag
- On order submission — check the flag; without it the order is rejected
OTP generation and sending
// /local/ajax/phone-otp-send.php
\Bitrix\Main\Application::getInstance()->initializeExtended();
$phone = preg_replace('/\D/', '', $_POST['phone'] ?? '');
$csrfOk = check_bitrix_sessid();
if (!$csrfOk || strlen($phone) < 10 || strlen($phone) > 15) {
http_response_code(400);
echo json_encode(['error' => 'Invalid request']);
exit;
}
// Limit: no more than 3 sends per number per 10 minutes
$cacheKey = 'otp_attempts_' . md5($phone);
$attempts = (int)(\Bitrix\Main\Application::getInstance()
->getManagedCache()->get($cacheKey) ?? 0);
if ($attempts >= 3) {
echo json_encode(['error' => 'Too many attempts. Please wait 10 minutes.']);
exit;
}
// Generate 6-digit code
$code = (string)random_int(100000, 999999);
$expiresAt = time() + 300; // 5 minutes
// Save to session (or Redis)
\Bitrix\Main\Application::getInstance()->getSession()->set('otp_data', [
'phone' => $phone,
'code' => password_hash($code, PASSWORD_DEFAULT),
'expires_at' => $expiresAt,
'verified' => false,
]);
// Increment attempt counter
\Bitrix\Main\Application::getInstance()->getManagedCache()->set($cacheKey, $attempts + 1, 600);
// Send SMS via 1C-Bitrix module (SMS provider configured in admin panel)
$smsManager = new \Bitrix\MessageService\Sender\MessageManager('sms');
$result = $smsManager->enqueueMessage([
'MESSAGE_TO' => '+' . $phone,
'MESSAGE_BODY' => "Your verification code: {$code}. Valid for 5 minutes.",
]);
echo json_encode([
'success' => $result->isSuccess(),
'expires_at' => $expiresAt,
'masked_phone' => '+' . substr($phone, 0, 3) . '***' . substr($phone, -2),
]);
Code verification
// /local/ajax/phone-otp-verify.php
\Bitrix\Main\Application::getInstance()->initializeExtended();
$inputCode = trim($_POST['code'] ?? '');
$session = \Bitrix\Main\Application::getInstance()->getSession();
$otpData = $session->get('otp_data');
if (!$otpData || time() > $otpData['expires_at']) {
echo json_encode(['success' => false, 'error' => 'Code expired. Please request a new one.']);
exit;
}
if (!password_verify($inputCode, $otpData['code'])) {
echo json_encode(['success' => false, 'error' => 'Invalid code.']);
exit;
}
// Mark phone as verified
$otpData['verified'] = true;
$session->set('otp_data', $otpData);
echo json_encode(['success' => true]);
Blocking orders without verification
AddEventHandler('sale', 'OnBeforeOrderFinalAction', function(\Bitrix\Sale\Order $order) {
if ($order->getId() > 0) return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
$otpData = \Bitrix\Main\Application::getInstance()->getSession()->get('otp_data');
$props = $order->getPropertyCollection();
$phone = preg_replace('/\D/', '', $props->getItemByOrderPropertyCode('PHONE')?->getValue() ?? '');
if (empty($otpData['verified'])
|| !$otpData['verified']
|| $otpData['phone'] !== $phone)
{
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::ERROR,
new \Bitrix\Main\Error('Please confirm your phone number.')
);
}
// Clear OTP after use
\Bitrix\Main\Application::getInstance()->getSession()->delete('otp_data');
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
});
Frontend: code input form
class PhoneVerification {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.phoneInput = this.form?.querySelector('[name="PHONE"]');
this.otpBlock = document.createElement('div');
this.countdown = null;
}
init() {
this.phoneInput?.addEventListener('blur', () => this.showOtpRequest());
}
async sendOtp() {
const phone = this.phoneInput.value;
const res = await fetch('/local/ajax/phone-otp-send.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `phone=${encodeURIComponent(phone)}&sessid=${BX.bitrix_sessid()}`,
}).then(r => r.json());
if (res.success) {
this.showCodeInput(res.expires_at, res.masked_phone);
} else {
this.showError(res.error);
}
}
showCodeInput(expiresAt, maskedPhone) {
this.otpBlock.innerHTML = `
<p>Code sent to ${maskedPhone}</p>
<input type="text" id="otp-code" maxlength="6" inputmode="numeric"
autocomplete="one-time-code" placeholder="_ _ _ _ _ _">
<button type="button" id="verify-btn">Confirm</button>
<span id="otp-timer"></span>
`;
this.startCountdown(expiresAt);
document.getElementById('verify-btn').addEventListener('click', () => this.verifyCode());
}
async verifyCode() {
const code = document.getElementById('otp-code').value;
const res = await fetch('/local/ajax/phone-otp-verify.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `code=${encodeURIComponent(code)}&sessid=${BX.bitrix_sessid()}`,
}).then(r => r.json());
if (res.success) {
this.otpBlock.innerHTML = '<p class="verified">✓ Phone confirmed</p>';
clearInterval(this.countdown);
// Unlock the checkout submit button
document.querySelector('.btn-checkout-submit')?.removeAttribute('disabled');
} else {
document.getElementById('otp-code').classList.add('is-error');
}
}
startCountdown(expiresAt) {
const timer = document.getElementById('otp-timer');
this.countdown = setInterval(() => {
const left = Math.max(0, expiresAt - Math.floor(Date.now() / 1000));
timer.textContent = `Code valid for ${left} sec`;
if (left === 0) {
clearInterval(this.countdown);
timer.textContent = 'Code expired. Please request a new one.';
}
}, 1000);
}
}
SMS providers in 1C-Bitrix
The messageservice module supports several providers out of the box: SMS.ru, SMSC.ru, MessageBird. Configuration via admin panel: Settings → SMS Services. A custom provider is added as a class implementing the \Bitrix\MessageService\Sender\Base interface.
Implementation timelines
| Configuration | Timeline |
|---|---|
| OTP (send + verify + order block) | 2–3 days |
| + frontend with countdown and feedback | +1–2 days |
| + custom SMS provider | +1 day |







