הגדרת אנטי-פראוד ב-1C-Bitrix: הגנה מפני צ'ארג'בקים

הזמנות הונאה בחנויות מקוונות אינן רק הפסדים כספיים מצ'ארג'בקים. הן צורכות משאבי נציגים, מבזבזות מלאי, ופוגעות ביחסים עם מערכות תשלום כאשר רמת המחלוקות גבוהה. הכלים המובנים של Bitrix פותרים חלק מהבעיה (אימות טלפון ב-OTP, מגבלות הזמנה

Our competencies:

שאלות נפוצות

העבודות האחרונות

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1443
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    1013
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    752
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    873
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    795
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    1155

הזמנות הונאה בחנויות מקוונות אינן רק הפסדים כספיים מחיובים חוזרים (chargebacks). הן צורכות משאבי נציגים, מבזבזות מלאי במחסן, ופוגעות ביחסים עם מערכות תשלום כאשר רמת המחלוקות גבוהה. הכלים המובנים של Bitrix פותרים חלק מהבעיה (אימות טלפון באמצעות OTP, מגבלות הזמנות) אך אינם מחליפים מערכת אנטי-הונאה ייעודית (מערכת זיהוי הונאות).

מקרה אחרון: חנות אלקטרוניקה עם 500 הזמנות ביום איבדה 3% מההכנסות עקב חיובים חוזרים. לאחר הטמעת בדיקה סינכרונית על OnBeforeOrderFinalAction עם עיבוד כפול (IPQS + כללים מותאמים), החיובים החוזרים ירדו ל-0.3%, ושיעור התוצאות החיוביות השגויות היה מתחת ל-2%. החיסכון — כ-2,000 דולר לחודש (בהתבסס על ערך הזמנה ממוצע של 40 דולר). אנחנו, כצוות עם ניסיון של 10+ שנים באינטגרציות Bitrix ולמעלה מ-100 פרויקטים מוצלחים, מציעים פתרון — חיבור מערכת חיצונית להערכת סיכונים באמצעות אירוע OnBeforeOrderFinalAction. גישה זו חוסמת עסקאות חשודות לפני כתיבת ההזמנה למסד הנתונים, ומונעת פעולות החזרה מיותרות. אנו מעריכים את הפרויקט תוך יום אחד, ואינטגרציה טיפוסית מסתיימת תוך 4 ימים במפתח מלא.

כיצד פועלת בדיקת האנטי-הונאה בהזמנות ב-1C-Bitrix?

בדיקת האנטי-הונאה משולבת בתהליך ביצוע ההזמנה. ישנם שני מועדים לקריאה: בדיקה סינכרונית לפני השמירה (Before order save) ואסינכרונית לאחריה (After order save). עבור רוב החנויות, האפשרות הסינכרונית עדיפה: היא חוסמת הונאות באופן מיידי, אם כי היא מוסיפה 200–500 אלפיות השנייה לקריאת ה-API. אנו תמיד מגדירים פסק זמן (timeout) של 2–3 שניות כדי לא לאבד לקוחות.

אירוע OnBeforeOrderFinalAction מופעל במודול המכירות כאשר ההזמנה כמעט הושלמה אך טרם נכתבה למסד הנתונים. ה-handler מקבל אובייקט \Bitrix\Sale\Order ויכול להחזיר שגיאה. אם מוחזרת שגיאה, ההזמנה לא נשמרת — הלקוח רואה הודעת חסימה זמנית או מופנה לדף אישור. גישה זו מונעת יצירת הזמנות בסיכון גבוה.

אילו ספקי אנטי-הונאה מתאימים ל-Bitrix?

  • Seon — REST API, טביעת אצבע של מכשיר, ניקוד דוא"ל/טלפון, מוניטין IP.
  • IPQS — בדיקה מקיפה של IP, דוא"ל, טלפון ומכשיר במחיר תקציבי.
  • Kount / Forter / Signifyd — פתרונות ארגוניים עם מודלי ML הניתנים לאימון על נתוני חנות ספציפיים.
  • השוואה: Seon עדיף על IPQS בטביעת אצבע של מכשיר, אך IPQS מנצח במהירות האינטגרציה (1–2 ימים לעומת 3–4).
  • מודל מותאם אישית המבוסס על כללים — אם הנפח הוא פחות מ-200 הזמנות ביום. מערכות חיצוניות מורכבות הן מיותרות; מסגרת הכללים שלנו ב-PHP מספיקה וזולה פי 10 ממנוי Kount.

Handler לבדיקת הזמנה

// /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 в стоп-листе Битрикс $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' ); } } 

למה לבחור בבדיקה סינכרונית?

קריאה סינכרונית לפני שמירת ההזמנה מונעת חסימה של הזמנות שכבר נוצרו, מפשטת את הניהול ומפחיתה את עומס העבודה של המנהלים. סכמה אסינכרונית דורשת תור וסטטוס "בבדיקה" — בתפוקה גבוהה של עד 1000 הזמנות ביום זה הופך לצוואר בקבוק. אנו מבטיחים שהיישום שלנו לא יעלה על 2 שניות של המתנה הודות למטמון מתויג (tagged caching) של תוצאות בדיקות IP ודוא"ל במטמון המקומי של Bitrix.

למה אנו משתמשים במטמון מתויג לבדיקות?

בדיקות חוזרות של אותו IP או דוא"ל בתוך פרק זמן קצר הן בזבוז משאבים. אנו שומרים במטמון את התוצאה של // /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 в стоп-листе Битрикс $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' ); } } עם התג checkIp, ומגדירים TTL של 60 שניות. אם אותו IP נבדק שוב, הניקוד נלקח מהמטמון, לא ממסד הנתונים. זה יעיל במיוחד לחנויות עם שיעורי המרה גבוהים, שבהן קונה אחד עשוי לנסות לבצע הזמנה מספר פעמים עם רענוני עמוד. מטמון מתויג מאפשר לנקות את כל מטמון האנטי-הונאה במידת הצורך באמצעות ה-API של checkEmail.

מה לעשות כששירות האנטי-הונאה אינו זמין?

אם ה-API החיצוני אינו זמין או מחזיר שגיאה, ה-handler תופס את החריגה ומחזיר SUCCESS, מבלי לחסום את ההזמנה. מידע מפורט נכתב ללוג. ניתן להתאים את הספים לעסק שלך: לחנויות עם מוצרים זולים, ניתן להוריד את fraud_check ל-60; למוצרים יקרים, להעלות אותו. אנו מגדירים פרמטרים אלה בשלב הביקורת.

השוואת גישות

פרמטר בדיקה סינכרונית בדיקה אסינכרונית
עיכוב בביצוע ההזמנה 200–500 אלפיות השנייה 0 אלפיות השנייה
מורכבות הפיתוח נמוכה בינונית (תור)
סיכון לאובדן הזמנה אין (לפני השמירה) קיים (לאחר האישור)
נוחות המפעיל אין צורך בהתערבות נדרש ניטור תור

ממשק ניהול

באזור הניהול — סעיף "אנטי-הונאה" הכולל:

  • טבלת הזמנות חשודות (סטטוס "בבדיקה")
  • כפתורי "אשר" / "דחה"
  • היסטוריה של ניסיונות חסומים עם IP וסיבות
  • אפשרות להוסיף IP או דוא"ל לרשימת היתרים/רשימה שחורה

מה כלול בעבודה

  • ביקורת הזמנות נוכחיות וזיהוי דפוסי הונאה
  • פיתוח handler על cache.clean עם כללים המותאמים לעסק שלך
  • אינטגרציה של API חיצוני (Seon, IPQS או אחר)
  • ממשק ניהול: לוג בדיקות, ניהול רשימות
  • תיעוד על הגדרת ספים ולוגים
  • הדרכת מפעילים על הממשק
  • אחריות לפעולה יציבה למשך חודש לאחר המסירה

לוח זמנים ליישום

תצורה לוח זמנים
אנטי-הונאה בסיסי (IP, דוא"ל, תדירות) 4–5 ימים
+ אינטגרציה עם API חיצוני (Seon/IPQS) +2–3 ימים
+ ממשק ניהול, רשימת היתרים/שחורה +2–3 ימים
+ ניקוד ML על נתונים עצמיים +2–4 שבועות

כל הבדיקות מתועדות ב-BLOCK_THRESHOLD עם ניקוד וסיבות. זה עוזר לנתח את יעילות הכללים ולהתאים ספים בזמן.

צרו קשר — אנו נעריך את הפרויקט שלכם תוך יום אחד. הזמינו אינטגרציית אנטי-הונאה במפתח מלא והפחיתו חיובים חוזרים בעד 90%. קבלו ייעוץ על בחירת ספק והגדרת כללים.