diff --git a/backend/app/Controllers/EndpointController.php b/backend/app/Controllers/EndpointController.php new file mode 100644 index 0000000..b797dc2 --- /dev/null +++ b/backend/app/Controllers/EndpointController.php @@ -0,0 +1,87 @@ +company_id); + $response->json([ + 'status' => 'success', + 'data' => $endpoints + ]); + } + + /** + * Create or update a custom API endpoint configuration + */ + public function store(Request $request, Response $response) + { + $errors = $this->validate($request, [ + 'name' => 'required', + 'endpoint_url' => 'required', + 'action_type' => 'required' + ]); + + if (!empty($errors)) { + $response->status(400)->json(['status' => 'error', 'errors' => $errors]); + return; + } + + $body = $request->getBody(); + $saveData = [ + 'company_id' => $request->company_id, + 'name' => $body['name'], + 'endpoint_url' => $body['endpoint_url'], + 'action_type' => $body['action_type'], + 'description' => $body['description'] ?? null, + 'headers' => $body['headers'] ?? null + ]; + + if (isset($body['id'])) { + $saveData['id'] = (int)$body['id']; + } + + $id = CompanyEndpoint::saveSecure($saveData); + + $response->json([ + 'status' => 'success', + 'message' => 'Integration endpoint saved successfully', + 'id' => $id + ]); + } + + /** + * Delete an API endpoint configuration + */ + public function delete(Request $request, Response $response) + { + $body = $request->getBody(); + if (empty($body['id'])) { + $response->status(400)->json(['status' => 'error', 'message' => 'Missing endpoint ID']); + return; + } + + // Verify ownership + $endpoint = CompanyEndpoint::find($body['id']); + if (!$endpoint || $endpoint['company_id'] !== $request->company_id) { + $response->status(404)->json(['status' => 'error', 'message' => 'Endpoint not found']); + return; + } + + CompanyEndpoint::delete($body['id']); + + $response->json([ + 'status' => 'success', + 'message' => 'Integration endpoint deleted successfully' + ]); + } +} diff --git a/backend/app/Controllers/WhatsAppController.php b/backend/app/Controllers/WhatsAppController.php index e7b5f40..16dfb8a 100644 --- a/backend/app/Controllers/WhatsAppController.php +++ b/backend/app/Controllers/WhatsAppController.php @@ -335,8 +335,21 @@ class WhatsAppController extends BaseController error_log("[Chatbot Warning] Gemini API Key is not set globally or for company " . $session['company_id']); return; } + + // Dynamically fetch customer/driver info from configured endpoints if set + $infoContext = ""; + $infoEndpoint = \App\Models\CompanyEndpoint::findByAction($session['company_id'], 'fetch_user_info'); + if ($infoEndpoint && !empty($msgData['phone'])) { + $infoContext = $this->fetchUserInfoFromEndpoint($infoEndpoint, $msgData['phone']); + } + $systemPrompt = $rule['ai_prompt'] ?: 'You are a helpful customer support assistant.'; + // Append real-time info context to Gemini system prompt + if (!empty($infoContext)) { + $systemPrompt .= "\n\n" . $infoContext; + } + // Enforce language matching rule dynamically $systemPrompt .= "\n\nIMPORTANT LANGUAGE RULE: Detect the language of the incoming message. If the incoming message is in English, you MUST reply in English. If the incoming message is in Arabic, you MUST reply in Arabic. Override any default language instruction to match the user's language."; @@ -368,8 +381,8 @@ class WhatsAppController extends BaseController // Strip the tag from the final reply sent to user $replyText = trim(str_replace($matches[0], '', $replyText)); - // Call the payment verification API - $verificationResult = $this->verifyPaymentSlip($msgData['phone'], $jsonStr); + // Call the payment verification API (passing company_id) + $verificationResult = $this->verifyPaymentSlip($session['company_id'], $msgData['phone'], $jsonStr); if ($verificationResult) { $replyText .= "\n\n" . $verificationResult; } @@ -436,9 +449,9 @@ class WhatsAppController extends BaseController } /** - * Call external Entaleq API to verify payment slip + * Call external API to verify payment slip */ - private function verifyPaymentSlip(string $phone, string $jsonStr): ?string + private function verifyPaymentSlip(int $companyId, string $phone, string $jsonStr): ?string { try { $data = json_decode($jsonStr, true); @@ -454,11 +467,17 @@ class WhatsAppController extends BaseController return null; } - // Determine API URL (default to localhost mock endpoint) - $apiUrl = getenv('ENTALEQ_PAYMENT_API_URL'); + // Find configured endpoint for verify_payment + $endpoint = \App\Models\CompanyEndpoint::findByAction($companyId, 'verify_payment'); + $apiUrl = $endpoint ? $endpoint['endpoint_url'] : null; + if (empty($apiUrl)) { - $appUrl = rtrim(getenv('APP_URL') ?: 'https://nabeh.intaleqapp.com', '/'); - $apiUrl = $appUrl . '/api/external/verify-payment'; + // Fallback to local default mock + $apiUrl = getenv('ENTALEQ_PAYMENT_API_URL'); + if (empty($apiUrl)) { + $appUrl = rtrim(getenv('APP_URL') ?: 'https://nabeh.intaleqapp.com', '/'); + $apiUrl = $appUrl . '/api/external/verify-payment'; + } } $payload = json_encode([ @@ -468,14 +487,23 @@ class WhatsAppController extends BaseController 'method' => $method ]); + $headers = ['Content-Type: application/json']; + if ($endpoint && !empty($endpoint['headers'])) { + $customHeaders = json_decode($endpoint['headers'], true); + if (is_array($customHeaders)) { + foreach ($customHeaders as $key => $value) { + $headers[] = "$key: $value"; + } + } + } else { + $headers[] = 'X-API-Key: ' . (getenv('ENTALEQ_API_KEY') ?: 'mock-key'); + } + $ch = curl_init($apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'X-API-Key: ' . (getenv('ENTALEQ_API_KEY') ?: 'mock-key') - ]); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); @@ -499,4 +527,45 @@ class WhatsAppController extends BaseController return "⏳ تم استلام وصل الدفع بنجاح. يجري الآن مراجعته وتدقيقه يدوياً من قبل الإدارة الفنية لتأكيد شحن رصيدك."; } } + + /** + * Fetch user/driver info from external API endpoint configured by the company + */ + private function fetchUserInfoFromEndpoint(array $endpoint, string $phone): string + { + try { + $apiUrl = $endpoint['endpoint_url']; + $payload = json_encode([ + 'phone' => $phone + ]); + + $headers = ['Content-Type: application/json']; + if (!empty($endpoint['headers'])) { + $customHeaders = json_decode($endpoint['headers'], true); + if (is_array($customHeaders)) { + foreach ($customHeaders as $key => $value) { + $headers[] = "$key: $value"; + } + } + } + + $ch = curl_init($apiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_TIMEOUT, 5); // Short timeout to avoid blocking chatbot flow + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode === 200 && !empty($response)) { + return "\n\n[معلومات العميل المسترجعة من النظام الخارجي للمؤسسة:\n" . $response . "\nاستخدم هذه المعلومات للإجابة بدقة على أسئلة العميل المتعلقة بحسابه ورصيده وحالته]"; + } + } catch (\Exception $e) { + error_log("[Fetch User Info Exception] " . $e->getMessage()); + } + return ""; + } } diff --git a/backend/app/Models/CompanyEndpoint.php b/backend/app/Models/CompanyEndpoint.php new file mode 100644 index 0000000..4c2f7da --- /dev/null +++ b/backend/app/Models/CompanyEndpoint.php @@ -0,0 +1,84 @@ +getMessage()); + } + } +} diff --git a/backend/public/favicon.svg b/backend/public/favicon.svg new file mode 100644 index 0000000..d8733b3 --- /dev/null +++ b/backend/public/favicon.svg @@ -0,0 +1,71 @@ + diff --git a/backend/public/index.html b/backend/public/index.html index 27a30b4..eccd1ff 100644 --- a/backend/public/index.html +++ b/backend/public/index.html @@ -4,6 +4,7 @@
Connected to system:
+Connected to system:
++ Configure external web APIs for multi-tenant integrations. The chatbot can fetch user profiles or verify payment details dynamically. +
+ +| Integration Name | +Action Type | +Endpoint URL | +Description | +Actions | +
|---|---|---|---|---|
| + | + + | ++ | + | + + + | +