Інтеграція 1С-Бітрікс з антифрод-системою
Шахрайські замовлення в інтернет-магазинах — це не лише фінансові втрати через чарджбеки. Це ресурси операторів, кинутий товар на складі, зіпсовані відносини з платіжними системами при високому рівні суперечок. Вбудовані інструменти 1С-Бітрікс вирішують частину проблем (OTP-верифікація телефону, ліміти замовлень), але не замінюють спеціалізований антифрод. Потрібна інтеграція із зовнішньою системою оцінки ризиків.
Архітектура інтеграції
Антифрод-перевірка вбудовується в процес оформлення замовлення. Є два моменти для виклику:
Before order save — до запису в базу. Якщо антифрод блокує, замовлення не створюється. Плюс: чисто. Мінус: синхронний виклик додає затримку до оформлення замовлення (200–500 мс на API-виклик).
After order save — замовлення створюється зі статусом «На перевірці», антифрод перевіряє асинхронно. Результат оновлює статус. Плюс: немає затримки. Мінус: потрібна обробка черги.
Для більшості магазинів — синхронна перевірка перед збереженням замовлення з тайм-аутом 2–3 секунди.
Провайдери антифроду
Seon — REST API, виконує device fingerprinting, email/phone scoring, IP reputation. Популярний в e-commerce.
IPQS (IPQualityScore) — комплексна перевірка IP, email, phone, device. Бюджетний варіант.
Kount / Forter / Signifyd — enterprise-рішення з ML-моделями, що навчаються на даних конкретного магазину.
Власна модель на базі правил — якщо обсяг менше 200 замовлень/день, складні зовнішні системи надлишкові. Набір перевірок на PHP достатній.
Обробник перевірки замовлення
// /local/lib/Fraud/FraudCheckHandler.php
namespace Local\Fraud;
AddEventHandler('sale', 'OnBeforeOrderFinalAction', [FraudCheckHandler::class, 'check']);
class FraudCheckHandler
{
public static function check(\Bitrix\Sale\Order $order): \Bitrix\Main\EventResult
{
if ($order->getId() > 0) {
// Вже існуюче замовлення, оновлення — пропускаємо
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
}
try {
$checker = new FraudChecker();
$result = $checker->evaluate($order);
if ($result->isBlocked()) {
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::ERROR,
new \Bitrix\Main\Error($result->getBlockReason())
);
}
if ($result->requiresReview()) {
// Позначаємо замовлення для ручної перевірки
$order->setField('COMMENTS', '[FRAUD_REVIEW] Score: ' . $result->getScore());
}
} catch (\Throwable $e) {
// Помилка антифроду не повинна блокувати замовлення
\Bitrix\Main\Diag\Debug::writeToFile(
['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()],
'Fraud check error',
'/local/logs/fraud.log'
);
}
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
}
}
Клас оцінки ризиків
namespace Local\Fraud;
class FraudChecker
{
private const BLOCK_THRESHOLD = 80;
private const REVIEW_THRESHOLD = 50;
public function evaluate(\Bitrix\Sale\Order $order): FraudResult
{
$score = 0;
$reasons = [];
$props = $order->getPropertyCollection();
$ip = $_SERVER['REMOTE_ADDR'];
$email = $props->getItemByOrderPropertyCode('EMAIL')?->getValue() ?? '';
$phone = $props->getItemByOrderPropertyCode('PHONE')?->getValue() ?? '';
// Перевірки по IP
$ipScore = $this->checkIp($ip);
$score += $ipScore['score'];
if ($ipScore['score'] > 20) $reasons[] = $ipScore['reason'];
// Перевірки по email
$emailScore = $this->checkEmail($email);
$score += $emailScore['score'];
if ($emailScore['score'] > 10) $reasons[] = $emailScore['reason'];
// Частота замовлень
$freqScore = $this->checkOrderFrequency($ip, $email, $phone);
$score += $freqScore['score'];
if ($freqScore['score'] > 15) $reasons[] = $freqScore['reason'];
// Сума замовлення
$amountScore = $this->checkOrderAmount($order);
$score += $amountScore['score'];
// Перевірка через зовнішній API (якщо налаштований)
if (defined('FRAUD_API_KEY') && FRAUD_API_KEY) {
$apiScore = $this->checkExternalApi($ip, $email, $phone, $order);
$score += $apiScore['score'];
if ($apiScore['score'] > 20) $reasons[] = $apiScore['reason'];
}
$this->log($order->getId() ?: 0, $ip, $email, $score, $reasons);
return new FraudResult($score, $reasons, self::BLOCK_THRESHOLD, self::REVIEW_THRESHOLD);
}
private function checkIp(string $ip): array
{
// VPN / Tor / datacenter IP — високий ризик
$conn = \Bitrix\Main\Application::getConnection();
// IP у стоп-листі 1С-Бітрікс
$inStopList = $conn->query(
"SELECT ID FROM b_stop_list WHERE IP_ADDR = '{$ip}' AND ACTIVE = 'Y' LIMIT 1"
)->fetch();
if ($inStopList) return ['score' => 60, 'reason' => 'IP in stop list'];
// Кількість замовлень з цього IP за останні 24 години
$orderCount = (int)$conn->query(
"SELECT COUNT(*) cnt FROM b_sale_order
WHERE CREATED_BY_IP = '{$ip}'
AND DATE_INSERT > DATE_SUB(NOW(), INTERVAL 24 HOUR)"
)->fetch()['cnt'];
if ($orderCount > 5) return ['score' => 40, 'reason' => "IP: {$orderCount} orders/24h"];
if ($orderCount > 2) return ['score' => 15, 'reason' => "IP: {$orderCount} orders/24h"];
return ['score' => 0, 'reason' => ''];
}
private function checkEmail(string $email): array
{
if (empty($email)) return ['score' => 20, 'reason' => 'No email'];
// Одноразові домени
$tempDomains = ['guerrillamail.com', 'mailinator.com', 'tempmail.com', 'throwam.com', 'yopmail.com'];
$domain = strtolower(substr(strrchr($email, '@'), 1));
if (in_array($domain, $tempDomains)) return ['score' => 40, 'reason' => 'Disposable email'];
// Кількість замовлень з цього email
$conn = \Bitrix\Main\Application::getConnection();
$emailSafe = $conn->getSqlHelper()->forSql($email);
$orderCount = (int)$conn->query(
"SELECT COUNT(*) cnt
FROM b_sale_order_props_value pv
JOIN b_sale_order_props p ON p.ID = pv.ORDER_PROPS_ID
JOIN b_sale_order o ON o.ID = pv.ORDER_ID
WHERE p.CODE = 'EMAIL'
AND pv.VALUE = '{$emailSafe}'
AND o.DATE_INSERT > DATE_SUB(NOW(), INTERVAL 7 DAY)"
)->fetch()['cnt'];
if ($orderCount > 3) return ['score' => 25, 'reason' => "Email: {$orderCount} orders/week"];
return ['score' => 0, 'reason' => ''];
}
private function checkOrderAmount(\Bitrix\Sale\Order $order): array
{
$price = (float)$order->getPrice();
// Дуже велике замовлення від нового покупця — ризик
$userId = (int)$order->getUserId();
if ($price > 100000 && $userId > 0) {
$conn = \Bitrix\Main\Application::getConnection();
$prevOrders = (int)$conn->query(
"SELECT COUNT(*) cnt FROM b_sale_order WHERE USER_ID = {$userId} AND STATUS_ID NOT IN ('C')"
)->fetch()['cnt'];
if ($prevOrders === 0) {
return ['score' => 30, 'reason' => 'High amount + new customer'];
}
}
return ['score' => 0, 'reason' => ''];
}
private function checkExternalApi(string $ip, string $email, string $phone, \Bitrix\Sale\Order $order): array
{
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . FRAUD_API_KEY);
$http->setTimeout(2); // жорсткий тайм-аут
$response = $http->post('https://api.fraudprovider.com/v1/check', json_encode([
'ip' => $ip,
'email' => $email,
'phone' => $phone,
'amount' => $order->getPrice(),
]));
if ($http->getStatus() !== 200) return ['score' => 0, 'reason' => ''];
$data = json_decode($response, true);
$risk = (int)($data['risk_score'] ?? 0);
return [
'score' => (int)($risk * 0.5), // нормалізуємо до нашої шкали
'reason' => $risk > 70 ? "External API risk: {$risk}" : '',
];
}
private function log(int $orderId, string $ip, string $email, int $score, array $reasons): void
{
\Bitrix\Main\Diag\Debug::writeToFile(
compact('orderId', 'ip', 'email', 'score', 'reasons'),
'Fraud check',
'/local/logs/fraud.log'
);
}
}
Адміністративний інтерфейс
В адміністративній частині — розділ «Антифрод» із:
- Таблицею підозрілих замовлень (статус «На перевірці»)
- Кнопками «Підтвердити» / «Відхилити»
- Історією заблокованих спроб із IP та причинами
- Можливістю додати IP або email до білого/чорного списку
Терміни реалізації
| Конфігурація | Термін |
|---|---|
| Базовий антифрод (IP, email, частота) | 4–5 днів |
| + інтеграція із зовнішнім API (Seon/IPQS) | +2–3 дні |
| + адміністративний інтерфейс, білі/чорні списки | +2–3 дні |
| + ML-скоринг на власних даних | +2–4 тижні |







