📚 Tài Liệu Debug: Facebook Messenger Bot

Migration Issues & Solutions - Complete Documentation

Ngày: 10/02/2026 | Version: 1.0

🎯 Tổng Quan Vấn Đề

Vấn Đề Ban Đầu

Triệu chứng: Bot không phản hồi tin nhắn mặc dù đã cấu hình kịch bản (scripts) trong database.

🔴 Hiện tượng quan sát được:

  • Webhook nhận tin nhắn từ Facebook ✅
  • Tin nhắn được lưu vào database ❓
  • Bot hoàn toàn im lặng - không phản hồi ❌
  • Không có lỗi rõ ràng trong log ⚠️

Context: Migration Architecture

Hệ thống đã được migrate từ single-user sang multi-user SAAS architecture:

Aspect Before (V4.x) After (V5.2)
Architecture Single-user (1 page) Multi-user SAAS (nhiều users, nhiều pages)
Primary Table facebook_config facebook_pages
Page Management 1 page cố định Multiple pages, is_active flag
Data Isolation Không cần Bắt buộc theo user_id

🔍 Phân Tích Nguyên Nhân Gốc Rễ

❌ LỖI #1: getUserIdFromPageId() Query Sai Bảng

Vấn Đề:

Function vẫn query từ bảng facebook_config trong khi data đã migrate sang facebook_pages

❌ CODE CŨ (SAI):

function getUserIdFromPageId($pdo, $page_id) { try { // ❌ Query sai bảng - facebook_config không còn có page_id $stmt = $pdo->prepare(" SELECT user_id FROM facebook_config WHERE page_id = ? LIMIT 1 "); $stmt->execute([$page_id]); $result = $stmt->fetch(); return $result['user_id'] ?? null; } catch(Exception $e) { return null; } }

Chuỗi Lỗi:

Webhook nhận page_id: 557849694089307
getUserIdFromPageId() query facebook_config
❌ Không tìm thấy (bảng không có page_id)
Return NULL
user_id = NULL → Bot không biết query scripts của ai
🔇 BOT IM LẶNG

✅ GIẢI PHÁP:

Query từ bảng facebook_pages

✅ CODE MỚI (ĐÚNG):

function getUserIdFromPageId($pdo, $page_id) { $debug_file = __DIR__ . '/webhook_processing_debug.log'; try { if (!$page_id || !$pdo) { error_log("⚠️ getUserIdFromPageId: Missing page_id or PDO"); return null; } // ✅ Query từ facebook_pages - ĐÚNG! $stmt = $pdo->prepare(" SELECT user_id, page_name, is_active FROM facebook_pages WHERE page_id = ? LIMIT 1 "); $stmt->execute([$page_id]); $result = $stmt->fetch(); if ($result && isset($result['user_id'])) { error_log("✅ Found user_id={$result['user_id']} from facebook_pages"); return $result['user_id']; } error_log("❌ CRITICAL: No user_id found for page_id: $page_id"); return null; } catch(Exception $e) { error_log("❌ Error: " . $e->getMessage()); return null; } }
📍 Location: File fb_webhook.php, dòng ~2700
⚡ Impact: CRITICAL - Toàn bộ hệ thống không hoạt động nếu không fix

❌ LỖI #2: getFacebookAccessToken() Query Sai Bảng

Vấn Đề:

Function query access token từ facebook_config nhưng token đã migrate sang facebook_pages

❌ CODE CŨ (SAI):

function getFacebookAccessToken($pdo, $user_id = null) { if (!$pdo || !$user_id) { return ''; } // ❌ Query sai bảng $stmt = $pdo->prepare(" SELECT page_access_token FROM facebook_config WHERE user_id = ? LIMIT 1 "); $stmt->execute([$user_id]); $result = $stmt->fetch(); return $result['page_access_token'] ?? ''; }
⚠️ Hậu quả:
  • Function trả về empty string
  • Bot không gửi được message về Facebook (vì không có token)
  • Ngay cả khi có response, user vẫn không nhận được

✅ GIẢI PHÁP:

Query từ facebook_pages với điều kiện is_active = 1

✅ CODE MỚI (ĐÚNG):

function getFacebookAccessToken($pdo, $user_id = null) { static $tokens = []; $cache_key = $user_id ?? 'no_user'; // Cache check if (isset($tokens[$cache_key])) { return $tokens[$cache_key]; } if (!$pdo || !$user_id) { return ''; } try { // ✅ Query từ facebook_pages - Page đang active $stmt = $pdo->prepare(" SELECT page_access_token, page_id, page_name FROM facebook_pages WHERE user_id = ? AND is_active = 1 AND page_access_token IS NOT NULL LIMIT 1 "); $stmt->execute([$user_id]); $page = $stmt->fetch(); if ($page && !empty($page['page_access_token'])) { $tokens[$cache_key] = $page['page_access_token']; error_log("✅ Token from active page '{$page['page_name']}'"); return $tokens[$cache_key]; } error_log("❌ No active page found for user_id: $user_id"); return ''; } catch(Exception $e) { error_log("❌ Error: " . $e->getMessage()); return ''; } }
📍 Location: File fb_webhook.php, dòng ~2450
⚡ Impact: HIGH - Bot không gửi được tin nhắn

❌ LỖI #3: User ID Isolation Failure

Vấn Đề:

Conversations và customers bị tạo với sai user_id (fallback về user_id = 1 hoặc NULL)

Tình huống thực tế:

-- Scripts thuộc user_id = 36 SELECT * FROM bot_scripts WHERE user_id = 36; -- → CÓ DATA ✅ -- Nhưng conversations có user_id sai SELECT user_id, message FROM conversations WHERE sender_id = '24502905112710899'; -- → user_id = 1 hoặc NULL ❌ -- Kết quả: Bot query scripts của user 1, không tìm thấy script của user 36 SELECT * FROM bot_scripts WHERE user_id = 1; -- → EMPTY ❌ → Bot im lặng

Data Mismatch:

Table user_id Status
bot_scripts 36 ✅ Đúng
facebook_pages 36 ✅ Đúng
conversations 1 / NULL ❌ SAI
customers 1 / NULL ❌ SAI

✅ GIẢI PHÁP:

Bắt buộc lấy user_id từ page_id, KHÔNG FALLBACK

✅ FIX trong processWebhookTextMessageEnhanced():

// ✅ LẤY user_id TỪ page_id NGAY ĐẦU $user_id = getUserIdFromPageId($pdo, $page_id); if (!$user_id) { error_log("❌ No user_id found - Bot stays SILENT"); return; // KHÔNG XỬ LÝ TIẾP } // ✅ LƯU VỚI user_id ĐÚNG $stmt = $pdo->prepare(" INSERT INTO conversations (sender_id, message, message_id, is_processed, user_id) VALUES (?, ?, ?, TRUE, ?) "); $stmt->execute([$sender_id, $message_text, $message_id, $user_id]);

✅ FIX trong updateCustomerInfo():

function updateCustomerInfo($facebook_id, $pdo, $page_id = null) { // ... validation code ... $stmt = $pdo->prepare(" SELECT id, user_id FROM customers WHERE facebook_id = ? "); $stmt->execute([$facebook_id]); $customer = $stmt->fetch(); if ($customer) { // Update existing $stmt = $pdo->prepare(" UPDATE customers SET last_message_time = NOW(), total_messages = total_messages + 1 WHERE facebook_id = ? "); $stmt->execute([$facebook_id]); } else { // ✅ BẮT BUỘC LẤY user_id TỪ page_id $user_id = null; if ($page_id) { $stmt = $pdo->prepare(" SELECT user_id FROM facebook_pages WHERE page_id = ? AND is_active = 1 "); $stmt->execute([$page_id]); $result = $stmt->fetch(); if ($result) { $user_id = $result['user_id']; } } // ❌ KHÔNG FALLBACK - Nếu không có user_id → KHÔNG TẠO if (!$user_id) { error_log("❌ Cannot find user_id - Customer NOT created"); return; } // ✅ Tạo với user_id ĐÚNG $stmt = $pdo->prepare(" INSERT INTO customers (user_id, facebook_id, last_message_time, total_messages, is_active, created_at) VALUES (?, ?, NOW(), 1, 1, NOW()) "); $stmt->execute([$user_id, $facebook_id]); } }

⚠️ LỖI #4: Message Cache Duplicate Check Bug

Vấn Đề:

markMessageAsProcessed() trả về false ngay cả khi message chưa được xử lý

Triệu chứng trong log:

markMessageAsProcessed called: Message ID: m_xxx... Sender ID: 24502905112710899 Existing in cache: NO Execute result: TRUE Affected rows: 0 ← ❌ VẤN ĐỀ Ở ĐÂY ⚠️ FAILED: affected_rows = 0 Marked as processed: NO (duplicate)
⚠️ Nguyên nhân có thể:
  • Table structure issue với UNIQUE key
  • INSERT IGNORE không insert nhưng cũng không báo lỗi
  • affected_rows = 0 mặc dù execute() trả về TRUE

✅ GIẢI PHÁP TẠM THỜI:

Skip duplicate check để test, log warning nhưng vẫn tiếp tục xử lý

✅ FIX:

// ✅ ĐÁNH DẤU ĐÃ XỬ LÝ if ($pdo) { $marked = markMessageAsProcessed($message_id, $sender_id, $pdo); // ⚠️ TẠM THỜI TẮT DUPLICATE CHECK ĐỂ BOT HOẠT ĐỘNG // TODO: Fix message_cache table structure issue /* if (!$marked) { error_log("⛔ Failed to mark - SKIPPING"); return; } */ // ✅ CHỈ LOG WARNING, KHÔNG RETURN if (!$marked) { error_log("⚠️ Warning: Could not mark, but continuing..."); } }
📍 Location: File fb_webhook.php, trong processWebhookTextMessageEnhanced()
⚡ Impact: MEDIUM - Tạm thời skip để test, cần fix vĩnh viễn sau

🔄 Workflow Hoàn Chỉnh

❌ Workflow CŨ (LỖI):

1. Webhook nhận tin nhắn

✅ Page ID: 557849694089307

✅ Sender ID: 24502905112710899

✅ Message: "test"

2. getUserIdFromPageId()

❌ Query: SELECT user_id FROM facebook_config WHERE page_id = ?

❌ Result: NULL (bảng không có page_id)

3. Lưu conversations

user_id = NULL

❌ Data isolation thất bại

4. findBotResponseEnhanced()

❌ Query: SELECT * FROM bot_scripts WHERE user_id = NULL

❌ Result: EMPTY

5. Bot Response

❌ Type: SILENT

❌ No message sent to Facebook

✅ Workflow MỚI (ĐÚNG):

1. Webhook nhận tin nhắn

✅ Page ID: 557849694089307

✅ Sender ID: 24502905112710899

✅ Message: "test"

2. getUserIdFromPageId()

✅ Query: SELECT user_id FROM facebook_pages WHERE page_id = ?

✅ Result: user_id = 36

3. Lưu conversations

user_id = 36

✅ Data isolation thành công

4. findBotResponseEnhanced()

✅ Query: SELECT * FROM bot_scripts WHERE user_id = 36

✅ Result: Script ID 25 (Multi-step)

5. executeMultiStepScript()

✅ Script executed successfully

✅ Multiple messages sent

6. getFacebookAccessToken()

✅ Query: SELECT page_access_token FROM facebook_pages WHERE user_id = 36

✅ Token found

7. Bot Response

✅ Messages sent to Facebook successfully

✅ User receives responses

🛠️ Hướng Dẫn Triển Khai Fix

Checklist Thực Hiện

  • Backup database và code trước khi sửa
  • Sửa getUserIdFromPageId() - Query từ facebook_pages
  • Sửa getFacebookAccessToken() - Query từ facebook_pages
  • Sửa processWebhookTextMessageEnhanced() - Lấy user_id từ page_id
  • Sửa updateCustomerInfo() - Không fallback user_id
  • Tạm thời skip duplicate check để test
  • Upload code lên server
  • Test với tin nhắn thực tế
  • Verify data trong database
  • Monitor webhook logs

Testing & Verification

1. Test Webhook Reception:

# Gửi tin nhắn vào Facebook Page Message: "test 123" # Check webhook log tail -f webhook_processing_debug.log # Expected output: ✅ Found from facebook_pages: - user_id: 36 - page_name: DTD Agency User ID: 36 Saving to conversations table... Insert result: SUCCESS

2. Verify Database:

-- Check conversations có đúng user_id SELECT user_id, sender_id, message, created_at FROM conversations ORDER BY created_at DESC LIMIT 5; -- Expected: user_id = 36 ✅ -- Check bot có phản hồi SELECT message, response, response_type FROM conversations WHERE user_id = 36 ORDER BY created_at DESC LIMIT 5; -- Expected: response_type = 'multi_step' hoặc 'script' ✅

3. Test với Keyword:

-- Xem keyword trong scripts SELECT keyword, is_multi_step FROM bot_scripts WHERE user_id = 36 AND is_active = 1;

Gửi tin nhắn với keyword đó vào page → Bot phải phản hồi ✅

🗄️ Database Schema Reference

Bảng facebook_pages (Primary Table)

CREATE TABLE facebook_pages ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, page_id VARCHAR(50) NOT NULL, page_name VARCHAR(255), page_access_token TEXT, is_active TINYINT(1) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY unique_user_page (user_id, page_id) ); -- Important: Chỉ 1 page active per user -- is_active = 1 → page này đang được sử dụng -- page_access_token → dùng để gửi tin nhắn về Facebook

Bảng facebook_config (Settings Only)

CREATE TABLE facebook_config ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL UNIQUE, page_id VARCHAR(50), -- ✅ CẦN CÓ để getUserIdFromPageId() hoạt động ai_enabled TINYINT(1) DEFAULT 1, ai_provider VARCHAR(20) DEFAULT 'chatgpt', gemini_api_key TEXT, chatgpt_api_key TEXT, -- ... other AI settings ... ); -- Note: Bảng này chỉ chứa settings, KHÔNG còn chứa page_access_token

Data Isolation Pattern

Nguyên tắc quan trọng:

  • MỌI query phải filter theo user_id
  • KHÔNG bao giờ fallback về user_id = 1 hoặc mặc định
  • Nếu không tìm được user_id → Bot im lặng (đúng hành vi)
  • Mỗi user chỉ thấy data của mình

Example Queries:

-- ✅ ĐÚNG - Filter theo user_id SELECT * FROM bot_scripts WHERE user_id = 36 AND is_active = 1; -- ✅ ĐÚNG - Chỉ lấy page đang active SELECT page_access_token FROM facebook_pages WHERE user_id = 36 AND is_active = 1; -- ❌ SAI - Không filter user_id SELECT * FROM bot_scripts WHERE is_active = 1; -- ❌ SAI - Fallback về user_id = 1 $user_id = getUserIdFromPageId($pdo, $page_id) ?? 1;

📚 Bài Học Rút Ra

1. Migration Must Be Complete

Khi migrate architecture, phải update TẤT CẢ functions query database. Không được bỏ sót một function nào.

Checklist khi migrate:
  • ✅ Search toàn bộ code với keyword của bảng cũ (vd: "facebook_config")
  • ✅ List tất cả functions query bảng đó
  • ✅ Update từng function một
  • ✅ Test từng function sau khi update
  • ✅ Integration test toàn bộ workflow

2. Data Isolation Is Critical

Trong multi-user system, KHÔNG BAO GIỜ fallback về user_id mặc định. Tốt hơn là bot im lặng còn hơn phản hồi sai data.

Nguyên tắc vàng:
  • Không có user_id → Không xử lý
  • Log error rõ ràng
  • Bot im lặng là hành vi đúng (không leak data)

3. Debug Systematically

Debug theo workflow từ đầu đến cuối:

  1. Webhook có nhận tin nhắn không?
  2. page_id có đúng không?
  3. user_id có được lấy ra không?
  4. Conversations có được lưu không?
  5. Scripts có được query không?
  6. Keyword có match không?
  7. Response có được generate không?
  8. Token có hợp lệ không?
  9. Message có được gửi về Facebook không?

4. Logging Is Essential

Thêm logging chi tiết tại mọi bước quan trọng:

// ✅ GOOD - Log đầy đủ $user_id = getUserIdFromPageId($pdo, $page_id); error_log("getUserIdFromPageId: page_id=$page_id → user_id=" . ($user_id ?? 'NULL')); if (!$user_id) { error_log("❌ CRITICAL: No user_id - Bot stays SILENT"); return; } // ❌ BAD - Không log gì $user_id = getUserIdFromPageId($pdo, $page_id); if (!$user_id) return;

⚡ Quick Reference

3 Functions Cần Fix

Function File Line ~ Fix
getUserIdFromPageId() fb_webhook.php 2700 Query từ facebook_pages
getFacebookAccessToken() fb_webhook.php 2450 Query từ facebook_pages
updateCustomerInfo() fb_webhook.php 2600 Không fallback user_id

SQL Queries Hữu Ích

Debug user_id mapping:

-- Verify page → user mapping SELECT page_id, page_name, user_id, is_active FROM facebook_pages WHERE page_id = '557849694089307'; -- Check conversations có đúng user_id SELECT user_id, sender_id, message, created_at FROM conversations ORDER BY created_at DESC LIMIT 10; -- Verify scripts của user SELECT COUNT(*) as total_scripts FROM bot_scripts WHERE user_id = 36 AND is_active = 1;

Fix data issues:

-- Update conversations với user_id đúng UPDATE conversations SET user_id = 36 WHERE sender_id = '24502905112710899' AND user_id IS NULL; -- Update customers với user_id đúng UPDATE customers SET user_id = 36 WHERE facebook_id = '24502905112710899'; -- Add page_id vào facebook_config (nếu thiếu) UPDATE facebook_config SET page_id = '557849694089307' WHERE user_id = 36;