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 mtn_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,100 @@
<?php
// --- create_mtn_invoice.php ---
// إذا كانت هناك فاتورة Pending لليوم نفسه لنفس المستخدم: نُحدّثها
// غير ذلك: نُنشئ فاتورة جديدة
include "../../connect.php";
header('Content-Type: application/json');
try {
$userId = filterRequest("user_id"); // driverID أو passengerID
$userType = filterRequest("user_type"); // 'driver' أو 'passenger'
$amount = filterRequest("amount");
$mtnPhone = filterRequest("mtn_phone");
if (empty($userId) || empty($userType) || !is_numeric($amount) || $amount <= 0 || empty($mtnPhone)) {
echo json_encode(["status" => "failure", "message" => "Invalid input provided."]);
exit;
}
// نضمن اتساق العمليات
$con->beginTransaction();
// ابحث عن فاتورة PENDING لنفس المستخدم، منشأة "اليوم"
// ملاحظة: يتطلب وجود عمود created_at (TIMESTAMP) في الجدول
$sel = $con->prepare("
SELECT id, invoice_number
FROM mtn_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 mtn_invoices
SET amount = :amount,
mtn_phone = :mtn_phone,
updated_at = NOW()
WHERE id = :id
");
$upd->execute([
':amount' => $amount,
':mtn_phone'=> $mtnPhone,
':id' => $existing['id'],
]);
$con->commit();
echo json_encode([
"status" => "success",
"message" => "Invoice updated (pending, same day).",
"invoice_number" => $existing['invoice_number'],
"mode" => "updated"
]);
} else {
// لا يوجد سجل لليوم: ننشئ فاتورة جديدة
$invoiceNumber = "MTN-" . time() . mt_rand(100, 999);
$ins = $con->prepare("
INSERT INTO mtn_invoices
(invoice_number, user_id, user_type, amount, mtn_phone, status, created_at, updated_at)
VALUES
(:invoice_number, :user_id, :user_type, :amount, :mtn_phone, 'pending', NOW(), NOW())
");
$ins->execute([
':invoice_number' => $invoiceNumber,
':user_id' => $userId,
':user_type' => $userType,
':amount' => $amount,
':mtn_phone' => $mtnPhone
]);
$con->commit();
// تحديد الرقم أو الاسم المستعار للدفع
$mtnPaymentNumber = $_ENV['MTN_PAYMENT_NUMBER'] ?? getenv('MTN_PAYMENT_NUMBER') ?: '0930000000';
echo json_encode([
"status" => "success",
"message" => "Invoice created successfully.",
"invoice_number" => $invoiceNumber,
"mtn_payment_number" => $mtnPaymentNumber,
"mode" => "inserted"
]);
}
} catch (Throwable $e) {
if ($con && $con->inTransaction()) { $con->rollBack(); }
error_log("Error in create_mtn_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 finalizeMtnPayment(PDO $con, int $invoiceId): array
{
try {
// جلب تفاصيل الفاتورة
$stmt = $con->prepare("SELECT * FROM `mtn_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 = 'mtn_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,86 @@
<?php
// --- mtn_webhook_handler.php ---
// هذا هو الـ Webhook الرئيسي الذي يستقبل إشعار تأكيد الدفع من MTN
include "../../jwtconnect.php";
include "./finalize_payment.php"; // تضمين ملف إتمام العملية
header('Content-Type: application/json');
// **مهم جداً: التحقق من مصدر الطلب**
// يجب التحقق من أن هذا الطلب قادم فعلاً من MTN وليس من أي طرف آخر
// المثال التالي يستخدم مفتاح سري مشترك (Shared Secret)
$expectedToken = trim(file_get_contents('/home/intaleq-wallet/.mtnKey')); // يجب استبداله بتوكن حقيقي
$receivedToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
if ($receivedToken !== $expectedToken) {
http_response_code(401); // Unauthorized
echo json_encode(["status" => "error", "message" => "Authentication failed."]);
exit;
}
// قراءة البيانات القادمة من MTN (عادة تكون بصيغة 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 `mtn_invoices`
SET `status` = 'completed', `mtn_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 `mtn_invoices` WHERE `invoice_number` = :invoice_number");
$idStmt->execute([':invoice_number' => $invoiceNumber]);
$invoiceRecord = $idStmt->fetch();
$invoiceId = $invoiceRecord['id'];
$finalizationResult = finalizeMtnPayment($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 mtn_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,64 @@
<?php
// --- query_mtn_invoice.php ---
// هذا السكربت هو نقطة الـ Webhook التي سيستدعيها نظام MTN
// للاستعلام عن وجود فاتورة دفع معلقة للمستخدم قبل أن يدفع
include "../../jwtconnect.php"; // تأكد من أن هذا المسار صحيح
header('Content-Type: application/json');
// يمكن إضافة طبقة حماية هنا للتحقق من أن الطلب قادم من سيرفرات MTN
// مثلاً عبر التحقق من IP أو من وجود Secret Key في الـ Headers
// --- آلية الحماية ---
$shared_secret_key = trim(file_get_contents('/home/intaleq-wallet/.mtnKey'));
$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 {
// يفترض أن MTN سترسل رقم هاتف المستخدم للاستعلام عنه
//$mtnPhone = filterRequest("mtn_phone");
$mtnPhone = $_GET['phone_number'] ?? null;
if (empty($mtnPhone)) {
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 `mtn_invoices`
WHERE `mtn_phone` = :mtn_phone AND `status` = 'pending'
ORDER BY `created_at` DESC LIMIT 1"
);
$stmt->execute([':mtn_phone' => $mtnPhone]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if ($invoice) {
// تم العثور على فاتورة، يتم إرجاع تفاصيلها لنظام MTN
echo json_encode([
"status" => "success",
"statusInvoice" => "pending",
"invoice_number" => $invoice['invoice_number'],
"amount" => (float) $invoice['amount'],
"description" => "شحن نقاط في تطبيق انطلق", // وصف يظهر للمستخدم في تطبيق MTN
"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_mtn_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 mtn_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, "MTN", $proofText, $proofImageBase64);
if (isset($aiResult['verified']) && $aiResult['verified'] === true) {
$con->beginTransaction();
$upd = $con->prepare("UPDATE mtn_invoices SET status = 'completed', updated_at = NOW() WHERE id = :id AND status = 'pending'");
$upd->execute([':id' => $invoice['id']]);
if ($upd->rowCount() > 0) {
$finalizationResult = finalizeMtnPayment($con, $invoice['id']);
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 MTN verify: " . $e->getMessage());
echo json_encode(["status" => "error", "message" => "Server error."]);
}
?>