'error', 'message' => 'Unauthorized']); exit; } $json_data = file_get_contents('php://input'); $data = json_decode($json_data, true); if (!$data || empty($data['sender']) || empty($data['message'])) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'بيانات غير مكتملة. يجب إرسال sender و message.']); exit; } $sender = trim($data['sender']); $message = trim($data['message']); $db = Database::getInstance(); try { // 1. Save raw SMS log $smsId = \App\Core\Database::generateUuid(); $stmt = $db->prepare(" INSERT INTO raw_sms_log (id, sender, message_body, status, received_at) VALUES (?, ?, ?, 'pending', NOW()) "); $stmt->execute([$smsId, $sender, $message]); // 2. Try to auto-match with pending payments $matchResult = matchPayment($db, $smsId, $sender, $message); http_response_code(200); echo json_encode([ 'status' => 'success', 'message' => 'SMS received and processed.', 'matched' => $matchResult['matched'], 'details' => $matchResult['details'] ?? null, ], JSON_UNESCAPED_UNICODE); } catch (\Exception $e) { error_log("[sms/receive] Error: " . $e->getMessage()); http_response_code(200); // Return 200 so bot doesn't retry echo json_encode(['status' => 'error', 'message' => 'خطأ داخلي في المعالجة.']); } /** * Try to match the incoming SMS with a pending payment request. * * Matching logic: * 1. Extract reference number from SMS (formats: MSQ-XXXX, REF-XXXX, or plain digits) * 2. Extract amount from SMS * 3. Find pending payment request matching reference OR amount * 4. If matched → confirm payment → activate/extend subscription */ function matchPayment(\PDO $db, string $smsId, string $sender, string $message): array { // Extract reference number (MSQ-XXXX pattern or any 6+ digit number) $reference = null; if (preg_match('/MSQ-([A-Z0-9]{4,10})/i', $message, $m)) { $reference = 'MSQ-' . strtoupper($m[1]); } elseif (preg_match('/REF[:\s-]*([A-Z0-9]{4,12})/i', $message, $m)) { $reference = $m[1]; } // Extract amount (Arabic or English digits) $amount = null; $msgNormalized = strtr($message, ['٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9']); if (preg_match('/(\d+[\.,]?\d{0,3})\s*(دينار|JOD|JD)/iu', $msgNormalized, $m)) { $amount = (float)str_replace(',', '.', $m[1]); } elseif (preg_match('/(\d+[\.,]\d{2})/', $msgNormalized, $m)) { $amount = (float)str_replace(',', '.', $m[1]); } if (!$reference && !$amount) { // Can't match — mark SMS as unmatched $db->prepare("UPDATE raw_sms_log SET status = 'unmatched', processed_at = NOW() WHERE id = ?")->execute([$smsId]); return ['matched' => false, 'details' => 'لم يتم العثور على مرجع أو مبلغ في الرسالة']; } // Search for pending payment request $where = "pr.status = 'pending'"; $params = []; if ($reference) { $where .= " AND pr.reference_number = ?"; $params[] = $reference; } if ($amount) { $where .= " AND pr.amount = ?"; $params[] = $amount; } $stmt = $db->prepare(" SELECT pr.*, t.name as tenant_name FROM payment_requests pr LEFT JOIN tenants t ON pr.tenant_id = t.id WHERE {$where} ORDER BY pr.created_at DESC LIMIT 1 "); $stmt->execute($params); $payment = $stmt->fetch(); if (!$payment) { $db->prepare("UPDATE raw_sms_log SET status = 'unmatched', extracted_ref = ?, extracted_amount = ?, processed_at = NOW() WHERE id = ?") ->execute([$reference, $amount, $smsId]); return ['matched' => false, 'details' => "مرجع: {$reference}, مبلغ: {$amount} — لم يتطابق مع أي طلب دفع"]; } // MATCH FOUND — Process payment $db->beginTransaction(); try { // 1. Update payment request → confirmed $db->prepare(" UPDATE payment_requests SET status = 'confirmed', sms_log_id = ?, confirmed_at = NOW() WHERE id = ? ")->execute([$smsId, $payment['id']]); // 2. Update SMS log → matched $db->prepare(" UPDATE raw_sms_log SET status = 'matched', payment_request_id = ?, extracted_ref = ?, extracted_amount = ?, processed_at = NOW() WHERE id = ? ")->execute([$payment['id'], $reference, $amount, $smsId]); // 3. Activate/extend subscription $planMonths = (int)($payment['plan_months'] ?? 1); $db->prepare(" UPDATE subscriptions SET is_active = 1, started_at = COALESCE(started_at, NOW()), expires_at = DATE_ADD(COALESCE(expires_at, NOW()), INTERVAL ? MONTH), updated_at = NOW() WHERE tenant_id = ? ")->execute([$planMonths, $payment['tenant_id']]); // 4. Notify user \App\Services\SmartNotifications::send( $payment['tenant_id'], $payment['user_id'] ?? '', 'payment_confirmed', '✅ تم تأكيد الدفع!', "تم تأكيد دفعة بقيمة {$payment['amount']} دينار. اشتراكك فعّال الآن.", ['payment_id' => $payment['id'], 'amount' => $payment['amount']] ); // 5. Audit log AuditLogger::log('payment.auto_confirmed', 'payment', $payment['id'], null, [ 'sms_id' => $smsId, 'sender' => $sender, 'reference' => $reference, 'amount' => $amount, ], ['user_id' => 'system', 'tenant_id' => $payment['tenant_id'], 'role' => 'system']); $db->commit(); return [ 'matched' => true, 'details' => "تم مطابقة الدفعة: {$payment['amount']} دينار — الاشتراك مُفعّل", ]; } catch (\Exception $e) { $db->rollBack(); error_log("[sms/match] Failed: " . $e->getMessage()); return ['matched' => false, 'details' => 'خطأ أثناء تأكيد الدفعة']; } }