getBody(); $storeUrl = $body['store_url'] ?? ''; $consumerKey = $body['consumer_key'] ?? ''; $consumerSecret = $body['consumer_secret'] ?? ''; $webhookSecret = $body['webhook_secret'] ?? null; if (empty($storeUrl) || empty($consumerKey) || empty($consumerSecret)) { $response->status(400)->json([ 'status' => 'error', 'message' => 'Missing store_url, consumer_key, or consumer_secret' ]); return; } try { $storeId = WooCommerceStore::saveStore( $request->company_id, $storeUrl, $consumerKey, $consumerSecret, $webhookSecret ); // Generate delivery URL for webhooks to display to the user $appUrl = rtrim(getenv('APP_URL') ?: 'https://nabeh.intaleqapp.com', '/'); $webhookUrl = $appUrl . '/api/webhooks/woocommerce?company_id=' . $request->company_id; $response->json([ 'status' => 'success', 'message' => 'WooCommerce store connected successfully', 'webhook_url' => $webhookUrl ]); } catch (\Exception $e) { $response->status(500)->json([ 'status' => 'error', 'message' => 'Connection failed: ' . $e->getMessage() ]); } } /** * Get WooCommerce connection status. * Protected by AuthMiddleware. * Accessible via GET /api/integrations/woocommerce/status */ public function status(Request $request, Response $response) { $store = WooCommerceStore::findByCompany($request->company_id); if ($store) { $appUrl = rtrim(getenv('APP_URL') ?: 'https://nabeh.intaleqapp.com', '/'); $webhookUrl = $appUrl . '/api/webhooks/woocommerce?company_id=' . $request->company_id; $response->json([ 'status' => 'success', 'connected' => true, 'store_url' => $store['store_url'], 'webhook_url' => $webhookUrl, 'has_webhook_secret' => !empty($store['webhook_secret']) ]); } else { $response->json([ 'status' => 'success', 'connected' => false ]); } } /** * Disconnect WooCommerce integration. * Protected by AuthMiddleware. * Accessible via POST /api/integrations/woocommerce/disconnect */ public function disconnect(Request $request, Response $response) { WooCommerceStore::deleteByCompany($request->company_id); $response->json([ 'status' => 'success', 'message' => 'WooCommerce integration disconnected successfully' ]); } /** * Handle incoming webhook from WooCommerce. * Accessible via POST /api/webhooks/woocommerce?company_id=XYZ */ public function webhook(Request $request, Response $response) { $companyId = (int)($request->get('company_id') ?? 0); if (empty($companyId)) { $response->status(400)->json(['error' => 'Missing company_id query parameter']); return; } $store = WooCommerceStore::findByCompany($companyId); if (!$store) { $response->status(404)->json(['error' => 'Store connection details not found for this tenant']); return; } $rawPayload = file_get_contents('php://input'); // 1. Verify signature if webhook_secret is configured if (!empty($store['webhook_secret'])) { $signatureHeader = $request->getHeader('x-wc-webhook-signature') ?: ''; if (empty($signatureHeader)) { $response->status(401)->json(['error' => 'Missing x-wc-webhook-signature header']); return; } $calculated = base64_encode(hash_hmac('sha256', $rawPayload, $store['webhook_secret'], true)); if (!hash_equals($calculated, $signatureHeader)) { error_log("[WooCommerce Webhook Error] Signature mismatch for company {$companyId}"); $response->status(401)->json(['error' => 'Signature verification failed']); return; } } // 2. Parse payload $payload = json_decode($rawPayload, true); if (!$payload) { $response->status(400)->json(['error' => 'Invalid JSON payload']); return; } $topic = $request->getHeader('x-wc-webhook-topic') ?: ''; if (empty($topic)) { $response->status(400)->json(['error' => 'Missing x-wc-webhook-topic header']); return; } // 3. Process events if ($topic === 'order.created' || $topic === 'order.updated') { $this->handleOrderWebhook($companyId, $topic, $payload); } $response->json(['status' => 'success']); } /** * Process order webhook events and send automated WhatsApp message */ private function handleOrderWebhook(int $companyId, string $topic, array $order) { $orderId = $order['id'] ?? ''; $customerName = trim(($order['billing']['first_name'] ?? '') . ' ' . ($order['billing']['last_name'] ?? '')); if (empty($customerName)) { $customerName = 'عميلنا العزيز'; } $customerPhone = $order['billing']['phone'] ?? $order['shipping']['phone'] ?? ''; $status = $order['status'] ?? ''; $total = $order['total'] ?? ''; $currency = $order['currency'] ?? 'USD'; if (empty($customerPhone) || empty($orderId)) { return; } // Normalize phone number $customerPhone = preg_replace('/\D/', '', $customerPhone); if (substr($customerPhone, 0, 2) === '00') { $customerPhone = substr($customerPhone, 2); } // Normalize Saudi numbers starting with 05 if (strlen($customerPhone) === 9 && $customerPhone[0] === '5') { $customerPhone = '966' . $customerPhone; } elseif (strlen($customerPhone) === 10 && substr($customerPhone, 0, 2) === '05') { $customerPhone = '966' . substr($customerPhone, 1); } // Formulate Arabic message based on topic $message = ''; if ($topic === 'order.created') { $message = "مرحباً {$customerName}،\nتم استلام طلبك رقم ({$orderId}) بنجاح! 🎉\nإجمالي الفاتورة: {$total} {$currency}\nحالة الطلب الحالية: *قيد المراجعة*.\nشكرًا لتسوقك معنا!"; } elseif ($topic === 'order.updated') { $translatedStatus = $this->translateStatus($status); $message = "مرحباً {$customerName}،\nتم تحديث حالة طلبك رقم ({$orderId}) إلى: *{$translatedStatus}*.\n"; // If completed, add standard courier notice if ($status === 'completed') { $message .= "🚚 طلبك الآن في طريقه إليك! نتمنى لك تجربة ممتعة."; } } if (empty($message)) { return; } // Send WhatsApp Message via active session $session = WhatsAppSession::findByCompany($companyId); if (!$session || $session['status'] !== 'connected') { error_log("[WooCommerce Webhook Warning] Cannot send auto-notification: No active connected WhatsApp session for company: " . $companyId); return; } $this->sendWhatsAppNotification($session['session_key'], $customerPhone, $message); } /** * Translate WooCommerce order status to Arabic */ private function translateStatus(string $status): string { $translations = [ 'pending' => 'بانتظار الدفع', 'processing' => 'قيد التجهيز', 'on-hold' => 'قيد الانتظار', 'completed' => 'مكتمل', 'cancelled' => 'ملغي', 'refunded' => 'مسترجع', 'failed' => 'فشل الدفع', 'checkout-draft' => 'مسودة السلة' ]; return $translations[$status] ?? $status; } /** * Send a WhatsApp message through the gateway */ private function sendWhatsAppNotification(string $sessionKey, string $phone, string $message) { $gatewayUrl = rtrim(getenv('WHATSAPP_GATEWAY_URL') ?: 'http://localhost:3722', '/'); if (substr($gatewayUrl, -4) === '/api') { $sendUrl = $gatewayUrl . '/messages/send'; } else { $sendUrl = $gatewayUrl . '/api/messages/send'; } $payload = json_encode([ 'session_key' => $sessionKey, 'phone' => $phone, 'message' => $message ]); $ch = curl_init($sendUrl); 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-Webhook-Secret: ' . getenv('WEBHOOK_SECRET') ]); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { error_log("[WooCommerce Webhook Gateway Error] Failed to send WhatsApp notification. Gateway response: " . $response); } } }