Налаштування лімітів замовлень по IP-адресі 1С-Бітрікс
Одна IP-підмережа, 50 замовлень за 10 хвилин — або масовий шахрайський вкид, або баг на фронтенді з повторним відправленням форми. В обох випадках ліміти по IP захищають базу даних від сміття і гаманець від втрат.
Ліміти на рівні PHP
Перевірка в обробнику події перед збереженням замовлення:
namespace Local\Fraud;
class IpOrderLimiter
{
// Ліміти: [інтервал у хвилинах => максимум замовлень]
private const LIMITS = [
15 => 3, // не більше 3 замовлень за 15 хвилин
60 => 5, // не більше 5 замовлень за 1 годину
1440 => 15, // не більше 15 замовлень за добу
];
public static function check(string $ip): ?string
{
$conn = \Bitrix\Main\Application::getConnection();
$ipSafe = $conn->getSqlHelper()->forSql($ip);
foreach (self::LIMITS as $minutes => $maxOrders) {
$from = date('Y-m-d H:i:s', time() - $minutes * 60);
$count = (int)$conn->query(
"SELECT COUNT(*) cnt FROM b_sale_order
WHERE CREATED_BY_IP = '{$ipSafe}'
AND DATE_INSERT >= '{$from}'"
)->fetch()['cnt'];
if ($count >= $maxOrders) {
return "Перевищено ліміт замовлень з вашого IP. Спробуйте через " . self::cooldownMinutes($minutes, $maxOrders, $ip) . " хвилин.";
}
}
return null;
}
private static function cooldownMinutes(int $windowMinutes, int $max, string $ip): int
{
$conn = \Bitrix\Main\Application::getConnection();
$ipSafe = $conn->getSqlHelper()->forSql($ip);
$from = date('Y-m-d H:i:s', time() - $windowMinutes * 60);
// Знаходимо найраніше із останніх $max замовлень
$oldest = $conn->query(
"SELECT MIN(DATE_INSERT) dt FROM (
SELECT DATE_INSERT FROM b_sale_order
WHERE CREATED_BY_IP = '{$ipSafe}'
AND DATE_INSERT >= '{$from}'
ORDER BY DATE_INSERT ASC
LIMIT {$max}
) sub"
)->fetch()['dt'];
if (!$oldest) return $windowMinutes;
$oldestTs = strtotime($oldest);
return max(1, (int)ceil(($oldestTs + $windowMinutes * 60 - time()) / 60));
}
}
Обробник події:
AddEventHandler('sale', 'OnBeforeOrderFinalAction', function(\Bitrix\Sale\Order $order) {
if ($order->getId() > 0) return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$error = \Local\Fraud\IpOrderLimiter::check($ip);
if ($error) {
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::ERROR,
new \Bitrix\Main\Error($error)
);
}
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
});
Ліміти на рівні nginx (перший рубіж)
nginx обробляє ліміти до PHP, не витрачаючи серверні ресурси:
# /etc/nginx/conf.d/order-limit.conf
# Зона для сторінки оформлення замовлення
limit_req_zone $binary_remote_addr zone=checkout:10m rate=2r/m;
# Зона для AJAX-запитів створення замовлення
limit_req_zone $binary_remote_addr zone=order_ajax:10m rate=5r/m;
server {
# ...
location = /order/ {
limit_req zone=checkout burst=3 nodelay;
limit_req_status 429;
# ...
}
location ~ ^/local/ajax/(order|checkout) {
limit_req zone=order_ajax burst=5 nodelay;
limit_req_status 429;
add_header Retry-After 60;
# ...
}
}
Білий список IP
Легітимні партнери або внутрішні IP не повинні потрапляти під ліміти:
private static function isWhitelisted(string $ip): bool
{
$whitelist = [
'127.0.0.1',
'::1',
'10.0.0.0/8', // внутрішня мережа
'192.168.0.0/16',
];
foreach ($whitelist as $cidr) {
if (str_contains($cidr, '/')) {
if (self::ipInCidr($ip, $cidr)) return true;
} elseif ($ip === $cidr) {
return true;
}
}
return false;
}
private static function ipInCidr(string $ip, string $cidr): bool
{
[$subnet, $mask] = explode('/', $cidr);
return (ip2long($ip) & ~((1 << (32 - (int)$mask)) - 1)) === ip2long($subnet);
}
Логування та моніторинг
Кожне спрацювання ліміту логується:
\Bitrix\Main\Diag\Debug::writeToFile(
[
'ip' => $ip,
'limit' => "{$count}/{$maxOrders} за {$minutes} хв",
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'referer' => $_SERVER['HTTP_REFERER'] ?? '',
],
'IP limit triggered',
'/local/logs/ip-limits.log'
);
Агент раз на день парсить лог і надсилає звіт: топ-10 IP за блокуваннями, динаміка за тиждень.
Налаштування лімітів
| Сценарій магазину | Рекомендовані ліміти |
|---|---|
| Стандартний B2C | 3/15хв, 5/год, 15/доба |
| B2B із великими замовленнями | 5/15хв, 15/год, 50/доба |
| Розпродаж (тимчасово) | 10/15хв, 30/год |
Ліміти зберігаються в конфігу або опціях модуля — без деплою змінюються через адміністративну частину.







