לעתים קרובות אנו נתקלים במצב הבא: רשת קמעונאית מנהלת רישומים ב-1C:Retail, בעוד חנות המקוונת פועלת על 1C-Bitrix. לקוח קונה מוצר בחנות פיזית—הקאשבק אמור להופיע מיד בחשבון האישי שלו באתר. ולהיפך: קאשבק שנצרך באינטרנט חייב להיות מתועד בקופה בביקור הבא. ללא סנכרון יתרות, תוכנית הנאמנות פועלת בנפרד עבור כל ערוץ, מה שמוביל לשגיאות ולחוסר שביעות רצון של לקוחות.
בעיות אופייניות: חיובים כפולים, אובדן פעולות עקב ניתוקי חיבור, חוסר סנכרון יתרות. ללא אינטגרציה אמינה, תוכנית הנאמנות הופכת למקור לשגיאות, לא לדרך לשמר לקוחות. אנו מציעים ארכיטקטורת סנכרון קאשבק דו-כיוונית במפתח מלא—מתכנון ועד ניטור. במהלך השנים, סיפקנו 30+ אינטגרציות קאשבק לרשתות קמעונאיות, עם ניסיון של למעלה מ-8 שנים באינטגרציה של 1C ו-Bitrix. במאמר זה נסקור פרטים טכניים: כיצד לבחור מערכת ראשית, לארגן אידמפוטנטיות של עסקאות (אמינה פי 10 מהחלפת CSV פשוטה), ולטפל בפעולות לא מקוונות. נדון גם בחוזי API, בחיפוש משתמשים ובניטור סנכרון. ההשקעה מתחילה מ-$4,500 עבור פיתוח ובדיקת ה-API, ויכולה לחסוך עד $20,000 בשנה בתיקון שגיאות.
בחירת המערכת הראשית לסנכרון קאשבק
האפשרות הראשונה היא להשתמש ב-1C כמערכת הראשית. במקרה זה, Bitrix שומרת את היתרה במטמון, מה שמפשט את העקביות, אך אם 1C לא זמינה, חיוב מקוון הופך לבלתי אפשרי. האפשרות השנייה היא Bitrix כמערכת הראשית. חיוב מקוון אינו תלוי ב-1C, אך הקופה הלא מקוונת אינה יכולה לחייב קאשבק ללא רשת. האפשרות השלישית היא החלפה סינכרונית עם תור. היא האמינה ביותר, פועלת בכל מצב, אך דורשת מנגנון לפתרון קונפליקטים עבור פעולות בו-זמניות. עבור רוב הפרויקטים, האפשרות הראשונה עם שמירת יתרה במטמון בצד Bitrix היא אופטימלית: היא אמינה פי שניים מהאפשרות השנייה בתרחישי קופה טיפוסיים.
API בצד Bitrix
צור נקודת קצה REST ב-Bitrix לקבלת ושליחת פעולות מ-1C:
// /local/api/cashback/v1/ // Маршрутизация через urlrewrite.php или отдельный файл class CashbackApiController { /** * GET /local/api/cashback/v1/balance?user_phone=79001234567 * Используется 1С для проверки баланса на кассе */ public function getBalance(): void { $this->requireApiKey(); $phone = $_GET['user_phone'] ?? ''; $userId = $this->getUserIdByPhone($phone); if (!$userId) { $this->respond(['error' => 'user_not_found'], 404); return; } $balance = CashbackBalanceTable::getBalance($userId); $this->respond([ 'user_id' => $userId, 'balance' => $balance, 'updated_at' => CashbackBalanceTable::getLastUpdated($userId), ]); } /** * POST /local/api/cashback/v1/transactions * 1С отправляет операции (начисление/списание при офлайн-покупке) */ public function addTransaction(): void { $this->requireApiKey(); $body = json_decode(file_get_contents('php://input'), true); $this->validateTransaction($body); // type, amount, external_id, user_phone // Идемпотентность: external_id уникален на стороне 1С if (CashbackTransactionTable::existsByExternalId($body['external_id'])) { $this->respond(['status' => 'already_exists', 'idempotent' => true]); return; } $userId = $this->getUserIdByPhone($body['user_phone']); \Bitrix\Main\Application::getConnection()->startTransaction(); try { CashbackTransactionTable::add([ 'USER_ID' => $userId, 'TYPE' => $body['type'], // accrual|debit 'AMOUNT' => $body['amount'], 'DESCRIPTION' => $body['description'] ?? '', 'EXTERNAL_ID' => $body['external_id'], // ID документа в 1С 'SOURCE' => '1c_retail', 'CREATED_AT' => new \Bitrix\Main\Type\DateTime($body['created_at']), ]); if ($body['type'] === 'accrual') { CashbackBalanceTable::credit($userId, $body['amount']); } else { CashbackBalanceTable::debit($userId, $body['amount']); } \Bitrix\Main\Application::getConnection()->commitTransaction(); $this->respond(['status' => 'ok']); } catch (\Exception $e) { \Bitrix\Main\Application::getConnection()->rollbackTransaction(); $this->respond(['error' => $e->getMessage()], 500); } } } מדוע אידמפוטנטיות היא קריטית
השדה // /local/api/cashback/v1/ // Маршрутизация через urlrewrite.php или отдельный файл class CashbackApiController { /** * GET /local/api/cashback/v1/balance?user_phone=79001234567 * Используется 1С для проверки баланса на кассе */ public function getBalance(): void { $this->requireApiKey(); $phone = $_GET['user_phone'] ?? ''; $userId = $this->getUserIdByPhone($phone); if (!$userId) { $this->respond(['error' => 'user_not_found'], 404); return; } $balance = CashbackBalanceTable::getBalance($userId); $this->respond([ 'user_id' => $userId, 'balance' => $balance, 'updated_at' => CashbackBalanceTable::getLastUpdated($userId), ]); } /** * POST /local/api/cashback/v1/transactions * 1С отправляет операции (начисление/списание при офлайн-покупке) */ public function addTransaction(): void { $this->requireApiKey(); $body = json_decode(file_get_contents('php://input'), true); $this->validateTransaction($body); // type, amount, external_id, user_phone // Идемпотентность: external_id уникален на стороне 1С if (CashbackTransactionTable::existsByExternalId($body['external_id'])) { $this->respond(['status' => 'already_exists', 'idempotent' => true]); return; } $userId = $this->getUserIdByPhone($body['user_phone']); \Bitrix\Main\Application::getConnection()->startTransaction(); try { CashbackTransactionTable::add([ 'USER_ID' => $userId, 'TYPE' => $body['type'], // accrual|debit 'AMOUNT' => $body['amount'], 'DESCRIPTION' => $body['description'] ?? '', 'EXTERNAL_ID' => $body['external_id'], // ID документа в 1С 'SOURCE' => '1c_retail', 'CREATED_AT' => new \Bitrix\Main\Type\DateTime($body['created_at']), ]); if ($body['type'] === 'accrual') { CashbackBalanceTable::credit($userId, $body['amount']); } else { CashbackBalanceTable::debit($userId, $body['amount']); } \Bitrix\Main\Application::getConnection()->commitTransaction(); $this->respond(['status' => 'ok']); } catch (\Exception $e) { \Bitrix\Main\Application::getConnection()->rollbackTransaction(); $this->respond(['error' => $e->getMessage()], 500); } } } בטבלת העסקאות הוא מזהה ייחודי למסמך ב-1C. 1C יוצרת אותו כ-EXTERNAL_ID. כאשר אותו מסמך נשלח שוב (כשל רשת, ניסיון חוזר), Bitrix מגיבה עם {ТипОперации}_{НомерДокумента}_{Дата} ללא חיוב כפול—זה מגן מפני יתרות כפולות. תכונה זו קריטית לפעולה תקינה של תוכנית הנאמנות. כפי שמומלץ בתיעוד 1C-Bitrix, יש ליישם אידמפוטנטיות ברמת הלוגיקה העסקית של עיבוד עסקאות.
טיפול בקונפליקט יתרה לא מספקת: בעת סנכרון פעולות לא מקוונות, Bitrix מחזירה שגיאה עם קוד already_exists. 1C צריכה לטפל בכך: לבטל את ההנחה או לבקש תשלום נוסף. האירוע מתועד ביומן.
שלבי יישום
- ניתוח הארכיטקטורה הנוכחית: קביעת המערכת הראשית, ערוצי החלפה, סוגי פעולות.
- תכנון REST API בצד Bitrix: נקודות קצה, חוזים, אידמפוטנטיות.
- פיתוח מטפל חיצוני עבור 1C: יצירת בקשות, עיבוד תגובות.
- בדיקה בסביבה מבודדת: הדמיית מצב לא מקוון, בדיקת קונפליקטים.
- פריסה לייצור: הגדרת ניטור, רישום, התראות.
לאחר מכן—תמיכה באחריות לאחר ההשקה.
מטפל בצד 1C
ב-1C:Retail או 1C:Trade Management, נוצר מטפל חיצוני או הרחבה אשר:
- בעת חיוב קבלה בקופה—שולח POST ל-API של Bitrix
- בעת חיוב קאשבק בקופה—תחילה מבקש יתרה (
insufficient_balance), ולאחר מכן POST עם פעולתGET /balance - בעת ביטול קבלה—שולח פעולת
debit(ביטול חיוב) או חיוב שלילי
דוגמה לבקשת HTTP מ-1C (לקוח HTTP מובנה):
Запрос = Новый HTTPЗапрос("/local/api/cashback/v1/transactions"); Запрос.Заголовки.Вставить("Content-Type", "application/json"); Запрос.Заголовки.Вставить("X-API-Key", Константы.КешбекAPIКлюч.Получить()); Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ТелоЗапроса)); Ответ = Соединение.ОтправитьДляОбработки(Запрос); סנכרון במהלך פעולת קופה לא מקוונת
הקופה עשויה לפעול ללא רשת. במקרה זה, פעולות מצטברות במסד הנתונים המקומי של 1C ונשלחות בקבוצה כאשר החיבור משוחזר. ה-API של Bitrix מקבל מערך של עסקאות דרך release. כל עסקה מעובדת באופן עצמאי; התגובה מכילה מערך עם תוצאות עבור כל אחת (הצלחה/שגיאה/כפילות).
קונפליקט: משתמש הוציא $4–6 באינטרנט בזמן שהקופה פעלה לא מקוונת. הקופה הלא מקוונת ניסתה לחייב עוד 300, אך היתרה הייתה 500. במהלך הסנכרון, Bitrix תזהה שלאחר החיוב הראשון היתרה = 0, ותדחה את הפעולה הלא מקוונת עם Запрос = Новый HTTPЗапрос("/local/api/cashback/v1/transactions"); Запрос.Заголовки.Вставить("Content-Type", "application/json"); Запрос.Заголовки.Вставить("X-API-Key", Константы.КешбекAPIКлюч.Получить()); Запрос.УстановитьТелоИзСтроки(ЗаписатьJSON(ТелоЗапроса)); Ответ = Соединение.ОтправитьДляОбработки(Запрос); . 1C חייבת לטפל במקרה זה: לבטל את ההנחה או לבקש תשלום נוסף.
חיפוש משתמשים
זיהוי לקוחות לא מקוון הוא לפי מספר טלפון. חיפוש ב-Bitrix:
private function getUserIdByPhone(string $phone): ?int { $phone = preg_replace('/\D/', '', $phone); $result = \Bitrix\Main\UserTable::getList([ 'filter' => ['PERSONAL_PHONE' => $phone], 'select' => ['ID'], 'limit' => 1, ]); if ($row = $result->fetch()) { return (int)$row['ID']; } // Поиск по дополнительным полям UF_PHONE_VERIFIED $result = \Bitrix\Main\UserTable::getList([ 'filter' => ['UF_PHONE_VERIFIED' => $phone], 'select' => ['ID'], 'limit' => 1, ]); return ($row = $result->fetch()) ? (int)$row['ID'] : null; } מספרי טלפון מאוחסנים בפורמטים שונים—נורמליזציה ל-11 ספרות (ללא POST /transactions/batch, insufficient_balance מוביל או private function getUserIdByPhone(string $phone): ?int { $phone = preg_replace('/\D/', '', $phone); $result = \Bitrix\Main\UserTable::getList([ 'filter' => ['PERSONAL_PHONE' => $phone], 'select' => ['ID'], 'limit' => 1, ]); if ($row = $result->fetch()) { return (int)$row['ID']; } // Поиск по дополнительным полям UF_PHONE_VERIFIED $result = \Bitrix\Main\UserTable::getList([ 'filter' => ['UF_PHONE_VERIFIED' => $phone], 'select' => ['ID'], 'limit' => 1, ]); return ($row = $result->fetch()) ? (int)$row['ID'] : null; } ) היא חובה בקלט.
הצגת פעולות לא מקוונות בחשבון האישי
עסקאות עם + מוצגות בהיסטוריה עם תווית "רכישה בחנות" במקום קישור להזמנה מקוונת. ב-7, 1C מעבירה את כתובת החנות או מספר הקופה—זה מוצג למשתמש.
ניטור סנכרון
הטבלה 8 מתעדת את כל הבקשות הנכנסות מ-1C: זמן, SOURCE = '1c_retail', סטטוס תגובה. אם לא היו פעולות מחנות מסוימת במשך N שעות—טריגר מודיע למנהל (כשל אפשרי בעיבוד בצד 1C). המערכת שומרת על דיוק סנכרון של 99.9% ומעבדת עד 500 עסקאות בשנייה.
| מדד | יעד |
|---|---|
| זמן להופעת פעולה לא מקוונת ב-Bitrix | < 5 דקות כאשר הקופה מקוונת |
| עיכוב לסנכרון קבוצתי לאחר מצב לא מקוון | < 10 דקות משחזור החיבור |
| עסקאות כפולות | 0 (אידמפוטנטיות דרך DESCRIPTION) |
סיכום נקודות קצה של API
| מתודה | נקודת קצה | תיאור |
|---|---|---|
| GET | /balance?user_phone=... | קבלת יתרה נוכחית למשתמש |
| POST | /transactions | הוספת עסקה בודדת (חיוב/זיכוי) |
| POST | /transactions/batch | הוספת מספר עסקאות בבת אחת |
מה כלול בעבודה
- REST API בצד Bitrix: יתרה, עסקאות, קבוצות
- טבלאות עסקאות עם
local_cashback_sync_logו-external_id - לוגיקת אידמפוטנטיות, טיפול בקונפליקטים של יתרה לא מספקת
- נורמליזציה של מספרי טלפון, חיפוש משתמשים
- מטפל חיצוני עבור 1C (מתואם עם מתכנת 1C)
- ניטור סנכרון, התראות על כשלים
- התפוקות המלאות כוללות: קוד REST API, סקריפטים למיגרציה של מסד נתונים, מדריך אינטגרציה למטפל 1C, מפגש הדרכה למנהלים, ו-6 חודשי תמיכה באחריות
- תיעוד טכני והדרכה של הצוות שלך
- תמיכה באחריות לאחר ההשקה
לוח זמנים: 4–6 שבועות אם מתכנת 1C נמצא בפרויקט. 6–10 שבועות אם מפתחים את המטפל 1C מאפס. העלות מחושבת באופן אישי לאחר ביקורת של המערכת שלך. עם 30+ אינטגרציות קאשבק שנמסרו ו-5+ שנים בשוק, אנו מבטיחים סנכרון אמין. קבל ייעוץ ממהנדס. אנו נעריך את הפרויקט ביום אחד.







