Update: 2026-06-11 18:22:57

This commit is contained in:
Hamza-Ayed
2026-06-11 18:22:59 +03:00
parent c5170a88d2
commit 727068b668
629 changed files with 46050 additions and 46109 deletions

View File

@@ -0,0 +1,32 @@
<?php
// --- check_status.php ---
include "../../connect.php";
header('Content-Type: application/json');
try {
$invoiceNumber = filterRequest("invoice_number");
if (empty($invoiceNumber)) {
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
exit;
}
$stmt = $con->prepare("SELECT status FROM click_invoices WHERE invoice_number = :invoice_number LIMIT 1");
$stmt->execute([':invoice_number' => $invoiceNumber]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if ($invoice) {
echo json_encode([
"status" => "success",
"invoice_status" => $invoice['status']
]);
} else {
echo json_encode(["status" => "failure", "message" => "Invoice not found."]);
}
} catch (Exception $e) {
error_log("Error in check_status.php: " . $e->getMessage());
echo json_encode(["status" => "error", "message" => "Server error."]);
}
?>

View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS cliq_invoices (
id INT AUTO_INCREMENT PRIMARY KEY,
invoice_number VARCHAR(50) NOT NULL UNIQUE,
user_id VARCHAR(50) NOT NULL,
user_type VARCHAR(20) NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
cliq_phone VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

View File

@@ -0,0 +1,86 @@
<?php
// --- click_webhook_handler.php ---
// هذا هو الـ Webhook الرئيسي الذي يستقبل إشعار تأكيد الدفع من Click
include "../../jwtconnect.php";
include "./finalize_payment.php"; // تضمين ملف إتمام العملية
header('Content-Type: application/json');
// **مهم جداً: التحقق من مصدر الطلب**
// يجب التحقق من أن هذا الطلب قادم فعلاً من Click وليس من أي طرف آخر
// المثال التالي يستخدم مفتاح سري مشترك (Shared Secret)
$expectedToken = trim(file_get_contents('/home/intaleq-wallet/.clickKey')); // يجب استبداله بتوكن حقيقي
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
if ($receivedToken !== $expectedToken) {
http_response_code(401); // Unauthorized
echo json_encode(["status" => "error", "message" => "Authentication failed."]);
exit;
}
// قراءة البيانات القادمة من Click (عادة تكون بصيغة JSON في الـ body)
$json_data = file_get_contents('php://input');
$data = json_decode($json_data, true);
$invoiceNumber = $data['invoice_number'] ?? null;
$transactionId = $data['transaction_id'] ?? null;
$paymentStatus = $data['status'] ?? null;
if (empty($invoiceNumber) || empty($transactionId) || $paymentStatus !== 'success') {
http_response_code(400); // Bad Request
echo json_encode(["status" => "error", "message" => "Missing or invalid payment data."]);
exit;
}
try {
$con->beginTransaction();
// 1. البحث عن الفاتورة وتحديث حالتها
$stmt = $con->prepare(
"UPDATE `click_invoices`
SET `status` = 'completed', `click_transaction_id` = :transaction_id
WHERE `invoice_number` = :invoice_number AND `status` = 'pending'"
);
$stmt->execute([
':transaction_id' => $transactionId,
':invoice_number' => $invoiceNumber
]);
if ($stmt->rowCount() > 0) {
// تم تحديث الفاتورة بنجاح، الآن نقوم بإتمام العملية
$invoiceId = $con->lastInsertId(); // ملاحظة: هذا قد لا يعمل دائماً مع UPDATE، الأفضل جلب الـ ID
// جلب ID الفاتورة بعد التأكد من وجودها
$idStmt = $con->prepare("SELECT id FROM `click_invoices` WHERE `invoice_number` = :invoice_number");
$idStmt->execute([':invoice_number' => $invoiceNumber]);
$invoiceRecord = $idStmt->fetch();
$invoiceId = $invoiceRecord['id'];
$finalizationResult = finalizeClickPayment($con, $invoiceId);
if ($finalizationResult['success']) {
$con->commit();
echo json_encode(["status" => "success", "message" => "Transaction finalized."]);
} else {
$con->rollBack();
// يجب هنا التعامل مع الحالة التي فشل فيها الإيداع رغم نجاح الدفع
error_log("CRITICAL: Payment received for invoice {$invoiceNumber} but finalization failed.");
http_response_code(500);
echo json_encode(["status" => "error", "message" => "Finalization failed."]);
}
} else {
// لم يتم العثور على فاتورة معلقة بهذا الرقم (ربما تمت معالجتها سابقاً)
$con->rollBack();
http_response_code(404);
echo json_encode(["status" => "error", "message" => "Invoice not found or already processed."]);
}
} catch (Exception $e) {
$con->rollBack();
error_log("Error in click_webhook_handler.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(["status" => "error", "message" => "An internal server error occurred."]);
}
?>

View File

@@ -0,0 +1,92 @@
<?php
// --- create_cliq_invoice.php ---
include "../../connect.php";
header('Content-Type: application/json');
try {
$userId = filterRequest("user_id");
$userType = filterRequest("user_type");
$amount = filterRequest("amount");
$cliqPhone = filterRequest("cliq_phone");
if (empty($userId) || empty($userType) || !is_numeric($amount) || $amount <= 0 || empty($cliqPhone)) {
echo json_encode(["status" => "failure", "message" => "Invalid input provided."]);
exit;
}
$con->beginTransaction();
$sel = $con->prepare("
SELECT id, invoice_number
FROM cliq_invoices
WHERE user_id = :uid
AND user_type = :utype
AND status = 'pending'
AND DATE(created_at) = CURRENT_DATE
ORDER BY id DESC
LIMIT 1
");
$sel->execute([
':uid' => $userId,
':utype' => $userType,
]);
$existing = $sel->fetch(PDO::FETCH_ASSOC);
if ($existing) {
$upd = $con->prepare("
UPDATE cliq_invoices
SET amount = :amount,
cliq_phone = :cliq_phone,
updated_at = NOW()
WHERE id = :id
");
$upd->execute([
':amount' => $amount,
':cliq_phone' => $cliqPhone,
':id' => $existing['id'],
]);
$con->commit();
echo json_encode([
"status" => "success",
"message" => "Invoice updated.",
"invoice_number" => $existing['invoice_number'],
"mode" => "updated"
]);
} else {
$invoiceNumber = "CLIQ-" . time() . mt_rand(100, 999);
$ins = $con->prepare("
INSERT INTO cliq_invoices
(invoice_number, user_id, user_type, amount, cliq_phone, status, created_at, updated_at)
VALUES
(:invoice_number, :user_id, :user_type, :amount, :cliq_phone, 'pending', NOW(), NOW())
");
$ins->execute([
':invoice_number' => $invoiceNumber,
':user_id' => $userId,
':user_type' => $userType,
':amount' => $amount,
':cliq_phone' => $cliqPhone
]);
$con->commit();
$cliqAlias = $_ENV['CLIQ_ALIAS'] ?? getenv('CLIQ_ALIAS') ?: 'siro_cliq';
echo json_encode([
"status" => "success",
"message" => "Invoice created successfully.",
"invoice_number" => $invoiceNumber,
"cliq_alias" => $cliqAlias,
"mode" => "inserted"
]);
}
} catch (Throwable $e) {
if ($con && $con->inTransaction()) { $con->rollBack(); }
error_log("Error in create_cliq_invoice.php: " . $e->getMessage());
echo json_encode(["status" => "failure", "message" => "An internal server error occurred."]);
}
?>

View File

@@ -0,0 +1,106 @@
<?php
// --- finalize_payment.php ---
// يحتوي على الدوال المنطقية لإضافة الرصيد للمستخدمين بعد تأكيد الدفع
// ملاحظة: هذا الملف لا يتم استدعاؤه مباشرة، بل يتم تضمينه في mtn_webhook_handler.php
/**
* دالة مركزية لإتمام الدفع بعد التحقق منه
* @param PDO $con اتصال قاعدة البيانات
* @param int $invoiceId معرّف الفاتورة في جدول mtn_invoices
* @return array نتيجة العملية
*/
function finalizeClickPayment(PDO $con, int $invoiceId): array
{
try {
// جلب تفاصيل الفاتورة
$stmt = $con->prepare("SELECT * FROM `click_invoices` WHERE id = :id AND status = 'completed' LIMIT 1");
$stmt->execute([':id' => $invoiceId]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$invoice) {
return ['success' => false, 'message' => 'Invoice not found or not completed.'];
}
$userType = $invoice['user_type'];
$userId = $invoice['user_id'];
$amount = (float) $invoice['amount'];
$paymentMethod = 'click_cash'; // تحديد طريقة الدفع
// تحديد ما إذا كان المستخدم سائقاً أم راكباً
if ($userType === 'driver') {
return finalizeForDriver($con, $userId, $amount, $paymentMethod);
} elseif ($userType === 'passenger') {
return finalizeForPassenger($con, $userId, $amount, $paymentMethod);
} else {
return ['success' => false, 'message' => 'Unknown user type.'];
}
} catch (Exception $e) {
error_log("Finalization Exception: " . $e->getMessage());
return ['success' => false, 'message' => $e->getMessage()];
}
}
// --- دوال مساعدة خاصة بالسائق ---
function finalizeForDriver(PDO $con, int $driverId, float $amount, string $paymentMethod): array
{
// حساب قيمة البونص كما في الكود الأصلي
$bonusAmount = match ((int)$amount) {
10000 => 10000.0,
20000 => 21000.0,
40000 => 45000.0,
100000 => 110000.0,
default => $amount,
};
// إنشاء سجل دفع جديد والحصول على ID
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
if (!$paymentID) throw new Exception('Failed to generate payment ID for driver.');
// إضافة الرصيد لمحفظة السائق
$stmtDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
$stmtDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
if ($stmtDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed.');
// إضافة سجل محاسبي لمحفظة سفر
$stmtSefer = $con->prepare("INSERT INTO seferWallet (driverId, passengerId, amount, paymentMethod) VALUES (:driverId, 'driver', :amount, :paymentMethod)");
$stmtSefer->execute([':driverId' => $driverId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
if ($stmtSefer->rowCount() === 0) throw new Exception('Insert to seferWallet failed.');
return ['success' => true, 'message' => 'Driver wallets updated.'];
}
function generatePaymentID(PDO $con, string $driverId, float $amount, string $method): ?string {
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (`amount`, `payment_method`, `driverID`) VALUES (:amount, :method, :driverID)");
$stmt->execute([':driverID' => $driverId, ':amount' => $amount, ':method' => $method]);
return $stmt->rowCount() > 0 ? $con->lastInsertId() : null;
}
// --- دوال مساعدة خاصة بالراكب ---
function finalizeForPassenger(PDO $con, string $passengerId, float $amount, string $paymentMethod): array
{
// حساب البونص للراكب
$finalAmount = calculatePassengerBonus($amount);
// إضافة الرصيد لمحفظة الراكب
$stmtPassenger = $con->prepare("INSERT INTO passengerWallet (passenger_id, balance) VALUES (:id, :amount) ON DUPLICATE KEY UPDATE balance = balance + :amount");
$stmtPassenger->execute([':id' => $passengerId, ':amount' => $finalAmount]);
if ($stmtPassenger->rowCount() === 0) throw new Exception('Update passengerWallet failed.');
// إضافة سجل محاسبي لمحفظة سفر
$stmtSefer = $con->prepare("INSERT INTO seferWallet (passengerId, driverId, amount, paymentMethod) VALUES (:passengerId, 'passenger', :amount, :paymentMethod)");
$stmtSefer->execute([':passengerId' => $passengerId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
if ($stmtSefer->rowCount() === 0) throw new Exception('Insert to seferWallet for passenger failed.');
return ['success' => true, 'message' => 'Passenger wallets updated.'];
}
function calculatePassengerBonus(float $amount): float {
if ($amount == 20000) return 20500;
if ($amount == 40000) return 42500;
if ($amount == 100000) return 104000;
return $amount;
}
?>

View File

@@ -0,0 +1,64 @@
<?php
// --- query_click_invoice.php ---
// هذا السكربت هو نقطة الـ Webhook التي سيستدعيها نظام Click
// للاستعلام عن وجود فاتورة دفع معلقة للمستخدم قبل أن يدفع
include "../../jwtconnect.php"; // تأكد من أن هذا المسار صحيح
header('Content-Type: application/json');
// يمكن إضافة طبقة حماية هنا للتحقق من أن الطلب قادم من سيرفرات Click
// مثلاً عبر التحقق من IP أو من وجود Secret Key في الـ Headers
// --- آلية الحماية ---
$shared_secret_key = trim(file_get_contents('/home/intaleq-wallet/.clickKey'));
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
if ($receivedToken !== $shared_secret_key) {
http_response_code(401); // Unauthorized
echo json_encode(['status' => 'error', 'message' => 'Authentication failed. Invalid or missing token.']);
exit;
}
try {
// يفترض أن Click سترسل رقم هاتف المستخدم للاستعلام عنه
//$clickPhone = filterRequest("click_phone");
$clickPhone = $_GET['phone_number'] ?? null;
if (empty($clickPhone)) {
echo json_encode(["status" => "error", "message" => "Phone number is required."]);
http_response_code(400);
exit;
}
// البحث عن فاتورة معلقة لهذا الرقم
$stmt = $con->prepare(
"SELECT invoice_number, amount, user_id, user_type
FROM `click_invoices`
WHERE `click_phone` = :click_phone AND `status` = 'pending'
ORDER BY `created_at` DESC LIMIT 1"
);
$stmt->execute([':click_phone' => $clickPhone]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if ($invoice) {
// تم العثور على فاتورة، يتم إرجاع تفاصيلها لنظام Click
echo json_encode([
"status" => "success",
"statusInvoice" => "pending",
"invoice_number" => $invoice['invoice_number'],
"amount" => (float) $invoice['amount'],
"description" => "شحن نقاط في تطبيق انطلق", // وصف يظهر للمستخدم في تطبيق Click
"biller_name" => "Intaleq App"
]);
} else {
// لا توجد فاتورة معلقة
echo json_encode(["status" => "error", "message" => "No pending invoice found for this number."]);
http_response_code(404);
}
} catch (Exception $e) {
error_log("Error in query_click_invoice.php: " . $e->getMessage());
echo json_encode(["status" => "error", "message" => "Internal server error."]);
http_response_code(500);
}
?>

View File

@@ -0,0 +1,75 @@
<?php
// --- verify_payment_ai.php ---
include "../../connect.php";
include "./finalize_payment.php";
include "../GeminiAi.php";
header('Content-Type: application/json');
try {
$json_data = file_get_contents('php://input');
$data = json_decode($json_data, true) ?: $_POST;
$invoiceNumber = $data['invoice_number'] ?? '';
$proofText = $data['proof_text'] ?? '';
$proofImageBase64 = $data['proof_image_base64'] ?? '';
if (empty($invoiceNumber)) {
echo json_encode(["status" => "failure", "message" => "Invoice number is required."]);
exit;
}
if (empty($proofText) && empty($proofImageBase64)) {
echo json_encode(["status" => "failure", "message" => "Proof text or image is required."]);
exit;
}
$stmt = $con->prepare("SELECT id, amount FROM cliq_invoices WHERE invoice_number = :inv AND status = 'pending'");
$stmt->execute([':inv' => $invoiceNumber]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$invoice) {
echo json_encode(["status" => "failure", "message" => "Invoice not found or already processed."]);
exit;
}
$amount = $invoice['amount'];
$geminiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?: '';
if (empty($geminiKey)) {
echo json_encode(["status" => "error", "message" => "Gemini API key not configured."]);
exit;
}
$gemini = new GeminiAi($geminiKey);
$aiResult = $gemini->verifyPayment($invoiceNumber, $amount, "Cliq", $proofText, $proofImageBase64);
if (isset($aiResult['verified']) && $aiResult['verified'] === true) {
$con->beginTransaction();
$upd = $con->prepare("UPDATE cliq_invoices SET status = 'completed', updated_at = NOW() WHERE id = :id AND status = 'pending'");
$upd->execute([':id' => $invoice['id']]);
if ($upd->rowCount() > 0) {
$finalizationResult = finalizeClickPayment($con, $invoice['id']); // assume finalizeClickPayment exists in finalize_payment.php or rename it
if ($finalizationResult['success']) {
$con->commit();
echo json_encode(["status" => "success", "message" => "Payment verified and finalized."]);
} else {
$con->rollBack();
echo json_encode(["status" => "error", "message" => "Verification succeeded but finalization failed."]);
}
} else {
$con->rollBack();
echo json_encode(["status" => "error", "message" => "Invoice already processed."]);
}
} else {
$reason = $aiResult['reason'] ?? "AI rejected the proof.";
echo json_encode(["status" => "failure", "message" => "Verification failed: $reason"]);
}
} catch (Exception $e) {
error_log("Error in cliq verify: " . $e->getMessage());
echo json_encode(["status" => "error", "message" => "Server error: " . $e->getMessage()]);
}
?>