Update: 2026-07-13 04:13:50
This commit is contained in:
Executable
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$envFile = '/home/intaleq-walletintaleq/env/.env';
|
||||
if (!file_exists($envFile)) {
|
||||
$envFile = __DIR__ . '/../.env';
|
||||
}
|
||||
loadEnvironment($envFile);
|
||||
|
||||
// Get environment variables (You don't need user/pass for JWT auth itself)
|
||||
$secretKey = getenv('SECRET_KEY'); // Only need the secret key now
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: https://wallet.siromove.com");
|
||||
header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); // Adjust as needed
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||
header('Content-Type: application/json'); // Set content type to JSON
|
||||
|
||||
// Handle preflight requests (OPTIONS)
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
|
||||
// --- Database Connection (Still needed for your application logic) ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER'); // Still used for DB connection
|
||||
$pass = getenv('PASS'); // Still used for DB connection
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
// echo $con;
|
||||
// --- JWT Authentication ---
|
||||
include "functions.php"; // Include the functions file
|
||||
|
||||
$decodedToken = authenticateJWT(); // Call the authentication function
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log($e->getMessage());
|
||||
http_response_code(500); // Internal Server Error
|
||||
echo json_encode(['error' => 'A database error occurred.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Executable
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-walletintaleq/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
|
||||
$key = getenv('keyOfApp'); // 32 bytes
|
||||
$iv = getenv('initializationVector'); // 16 bytes
|
||||
|
||||
|
||||
class EncryptionHelper {
|
||||
private $key;
|
||||
private $iv;
|
||||
|
||||
public function __construct($key, $iv) {
|
||||
if (strlen($key) !== 32) {
|
||||
throw new Exception("❌ المفتاح (Key) لازم يكون 32 بايت.");
|
||||
}
|
||||
if (strlen($iv) !== 16) {
|
||||
throw new Exception("❌ الـ IV لازم يكون 16 بايت.");
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
$this->iv = $iv;
|
||||
}
|
||||
|
||||
// --------- النصوص ----------
|
||||
private function addPadding($data, $blockSize = 16) {
|
||||
$pad = $blockSize - (strlen($data) % $blockSize);
|
||||
return $data . str_repeat(chr($pad), $pad);
|
||||
}
|
||||
|
||||
private function removePadding($data) {
|
||||
$pad = ord($data[strlen($data) - 1]);
|
||||
return substr($data, 0, -$pad);
|
||||
}
|
||||
|
||||
public function encryptData($plainText) {
|
||||
$plainText = mb_convert_encoding($plainText, 'UTF-8');
|
||||
$paddedText = $this->addPadding($plainText);
|
||||
$iv = random_bytes(16);
|
||||
$encrypted = openssl_encrypt($paddedText, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
return base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
public function decryptData($encryptedText) {
|
||||
$decoded = base64_decode($encryptedText, true);
|
||||
|
||||
if ($decoded === false) {
|
||||
error_log("[ERROR] base64_decode failed for input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
// محاولة أولى: استخراج IV عشوائي من أول 16 بايت
|
||||
if (strlen($decoded) >= 16) {
|
||||
$iv = substr($decoded, 0, 16);
|
||||
$payload = substr($decoded, 16);
|
||||
|
||||
$decrypted = openssl_decrypt($payload, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($decrypted !== false) {
|
||||
$pad = ord($decrypted[strlen($decrypted) - 1]);
|
||||
if ($pad >= 1 && $pad <= 16) {
|
||||
return substr($decrypted, 0, -$pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// محاولة ثانية: IV ثابت (للبيانات القديمة)
|
||||
$decrypted = openssl_decrypt($decoded, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
|
||||
if ($decrypted === false) {
|
||||
error_log("[ERROR] openssl_decrypt failed for input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
$pad = ord($decrypted[strlen($decrypted) - 1]);
|
||||
if ($pad < 1 || $pad > 16) {
|
||||
error_log("[ERROR] Invalid padding value ($pad) for decrypted input: $encryptedText");
|
||||
return false;
|
||||
}
|
||||
|
||||
return substr($decrypted, 0, -$pad);
|
||||
}
|
||||
|
||||
public function decryptFile($encryptedFilePath, $destinationPath) {
|
||||
if (!file_exists($encryptedFilePath)) {
|
||||
throw new Exception("❌ الملف المشفر غير موجود: $encryptedFilePath");
|
||||
}
|
||||
|
||||
$encryptedData = file_get_contents($encryptedFilePath);
|
||||
$decryptedData = openssl_decrypt($encryptedData, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
|
||||
file_put_contents($destinationPath, $decryptedData);
|
||||
return true;
|
||||
}
|
||||
public function encryptBinary($data) {
|
||||
$iv = random_bytes(16);
|
||||
$encrypted = openssl_encrypt($data, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
return $iv . $encrypted;
|
||||
}
|
||||
|
||||
public function decryptBinary($data) {
|
||||
if (strlen($data) >= 16) {
|
||||
$iv = substr($data, 0, 16);
|
||||
$payload = substr($data, 16);
|
||||
$decrypted = openssl_decrypt($payload, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $iv);
|
||||
if ($decrypted !== false) {
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
// للبيانات القديمة ذات IV الثابت
|
||||
$decrypted = openssl_decrypt($data, 'AES-256-CBC', $this->key, OPENSSL_RAW_DATA, $this->iv);
|
||||
if ($decrypted === false) {
|
||||
error_log('[CRIT-07] openssl_decrypt failed in decryptBinary');
|
||||
throw new Exception('Decryption failed');
|
||||
}
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
// ✅ Load the key and IV from .env or use default values
|
||||
|
||||
// ✅ Ensure the lengths are correct
|
||||
//echo "Key Length: " . $key . PHP_EOL;
|
||||
//echo "IV Length: " . $iv . PHP_EOL;
|
||||
|
||||
try {
|
||||
$encryptionHelper = new EncryptionHelper($key, $iv);
|
||||
} catch (Exception $e) {
|
||||
error_log("[encrypt_decrypt] Initialization error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
?>
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Firebase\JWT\ExpiredException;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
use Firebase\JWT\BeforeValidException;
|
||||
|
||||
define("MB", 1048576);
|
||||
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// authenticateJWT — دالة التحقق من التوكن
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// استبدل الدالة الموجودة في functions.php بهذه
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// طبقات التحقق بالترتيب:
|
||||
// 1. وجود الـ JWT في Authorization header
|
||||
// 2. صحة التوقيع وعدم انتهاء الصلاحية
|
||||
// 3. صحة الـ Issuer
|
||||
// 4. مطابقة بصمة الجهاز (X-Device-FP header)
|
||||
// 5. مطابقة الـ HMAC — للـ wallet فقط (X-HMAC-Auth header)
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// جميع العمليات في الذاكرة — لا استعلامات DB
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function authenticateJWT(bool $isReg = false): object
|
||||
{
|
||||
$keyPath = getenv('WALLET_SECRET_KEY_PATH');
|
||||
$secretKey = '';
|
||||
if ($keyPath && file_exists($keyPath)) {
|
||||
$secretKey = trim(file_get_contents($keyPath));
|
||||
}
|
||||
if (!$secretKey) {
|
||||
$secretKey = getenv('SECRET_KEY') ?: '';
|
||||
}
|
||||
$hmacSecret = getenv('SECRET_KEY_HMAC');
|
||||
$fpPepper = getenv('FP_PEPPER');
|
||||
|
||||
error_log('[JWT_DEBUG] ── START authenticateJWT ──────────────────────────');
|
||||
error_log('[JWT_DEBUG] isReg=' . ($isReg ? 'true' : 'false'));
|
||||
error_log('[JWT_DEBUG] secretKey loaded: ' . (!empty($secretKey) ? 'YES (len=' . strlen($secretKey) . ')' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] hmacSecret loaded: ' . (!empty($hmacSecret) ? 'YES' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] fpPepper loaded: ' . (!empty($fpPepper) ? 'YES' : 'NO'));
|
||||
|
||||
if (!$secretKey || !$hmacSecret) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal server configuration error.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 1. استخراج الـ JWT ──────────────────────────────────────
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
||||
$token = null;
|
||||
|
||||
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
|
||||
$token = $matches[1];
|
||||
}
|
||||
|
||||
error_log('[JWT_DEBUG] Authorization header present: ' . (!empty($authHeader) ? 'YES' : 'NO ❌'));
|
||||
error_log('[JWT_DEBUG] Token extracted: ' . ($token ? 'YES (len=' . strlen($token) . ')' : 'NO ❌'));
|
||||
|
||||
if (!$token) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Authorization token required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 2. فك التشفير والتحقق ───────────────────────────────────
|
||||
try {
|
||||
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
|
||||
error_log('[JWT_DEBUG] JWT decode: SUCCESS ✅');
|
||||
error_log('[JWT_DEBUG] JWT payload: ' . json_encode((array)$decoded));
|
||||
|
||||
} catch (ExpiredException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: ExpiredException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Token expired']);
|
||||
exit;
|
||||
|
||||
} catch (SignatureInvalidException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: SignatureInvalidException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token signature']);
|
||||
exit;
|
||||
|
||||
} catch (BeforeValidException $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: BeforeValidException ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Token not yet valid']);
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[JWT_DEBUG] JWT decode FAILED: Exception ❌ | ' . $e->getMessage());
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. التحقق من الـ Issuer ─────────────────────────────────
|
||||
$expectedIssuer = 'Tripz-Wallet';
|
||||
$actualIssuer = $decoded->iss ?? '(missing)';
|
||||
error_log('[JWT_DEBUG] Issuer check | expected=' . $expectedIssuer . ' | actual=' . $actualIssuer);
|
||||
|
||||
if ($actualIssuer !== $expectedIssuer) {
|
||||
error_log('[JWT_DEBUG] Issuer MISMATCH ❌');
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid token issuer']);
|
||||
exit;
|
||||
}
|
||||
error_log('[JWT_DEBUG] Issuer: OK ✅');
|
||||
|
||||
// ── user_id ─────────────────────────────────────────────────
|
||||
$userId = $decoded->user_id ?? $decoded->sub ?? null;
|
||||
error_log('[JWT_DEBUG] user_id extracted: ' . ($userId ?? '(null) ❌'));
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Invalid JWT payload']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. بصمة الجهاز ──────────────────────────────────────────
|
||||
if (!$isReg && $fpPepper) {
|
||||
$fpInToken = $decoded->fingerPrint ?? null;
|
||||
$fpHeader = $_SERVER['HTTP_X_DEVICE_FP'] ?? null;
|
||||
|
||||
error_log('[JWT_DEBUG] FP check | fpInToken=' . ($fpInToken ?? '(null)'));
|
||||
error_log('[JWT_DEBUG] FP check | X-Device-FP header=' . ($fpHeader ?? '(null)'));
|
||||
|
||||
if ($fpInToken !== null && $fpHeader !== null) {
|
||||
$expectedFp = hash('sha256', $fpHeader . $fpPepper);
|
||||
$fpMatch = hash_equals($expectedFp, $fpInToken);
|
||||
|
||||
error_log('[JWT_DEBUG] FP check | expectedFp=' . $expectedFp);
|
||||
error_log('[JWT_DEBUG] FP check | match=' . ($fpMatch ? 'YES ✅' : 'NO ❌'));
|
||||
|
||||
if (!$fpMatch) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Device mismatch | user=%s | IP=%s',
|
||||
$userId,
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Device mismatch']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
error_log('[JWT_DEBUG] FP check SKIPPED (token or header is null) ⚠️');
|
||||
}
|
||||
} else {
|
||||
error_log('[JWT_DEBUG] FP check SKIPPED | isReg=' . ($isReg ? 'true' : 'false') . ' | fpPepper=' . (!empty($fpPepper) ? 'set' : 'empty'));
|
||||
}
|
||||
|
||||
// ── 5. HMAC ─────────────────────────────────────────────────
|
||||
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
|
||||
error_log('[JWT_DEBUG] HMAC header present: ' . ($hmacHeader !== null ? 'YES' : 'NO (skip)'));
|
||||
|
||||
if ($hmacHeader !== null) {
|
||||
$expectedHmac = hash_hmac('sha256', $userId, $hmacSecret);
|
||||
$hmacMatch = hash_equals($expectedHmac, $hmacHeader);
|
||||
|
||||
error_log('[JWT_DEBUG] HMAC check | match=' . ($hmacMatch ? 'YES ✅' : 'NO ❌'));
|
||||
|
||||
if (!$hmacMatch) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] HMAC mismatch | user=%s | IP=%s',
|
||||
$userId,
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid HMAC']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
error_log('[JWT_DEBUG] ── ALL CHECKS PASSED ✅ ─────────────────────────');
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function filterRequest($requestname, $type = 'string') {
|
||||
if (isset($_POST[$requestname]) && !empty($_POST[$requestname])) {
|
||||
$value = trim($_POST[$requestname]);
|
||||
// Remove any control characters
|
||||
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
|
||||
// Remove any HTML or XML tags
|
||||
$value = strip_tags($value);
|
||||
// Escape any special characters
|
||||
$value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
if ($type === 'numeric') {
|
||||
if (filter_var($value, FILTER_VALIDATE_FLOAT) !== false) {
|
||||
return $value;
|
||||
}
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function sendWhatsAppFromServer($to, $message)
|
||||
{
|
||||
// 1) قائمة السيرفرات المتاحة
|
||||
$servers = [
|
||||
"https://botmasa.intaleq.xyz/send",//rama tah
|
||||
"https://botmasa2.intaleq.xyz/send",//shad
|
||||
// "https://bootride.intaleq.xyz/send",//shahd bus
|
||||
//"https://bot3.intaleq.xyz/send",//shahd
|
||||
//"https://whatsapp.tripz-egypt.com/send"//tripz
|
||||
];
|
||||
|
||||
// 2) اختيار عشوائي
|
||||
$url = $servers[array_rand($servers)];
|
||||
|
||||
// 3) إعداد البيانات
|
||||
$payload = [
|
||||
"to" => $to,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
// 4) تنفيذ الطلب
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => "POST",
|
||||
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json"
|
||||
],
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
// 5) تسجيل النتيجة
|
||||
if ($err) {
|
||||
error_log("[sendWhatsAppFromServer] cURL Error on $url: $err");
|
||||
return false;
|
||||
}
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//////////
|
||||
|
||||
function printFailure($message = "none")
|
||||
{
|
||||
echo json_encode(array("status" => "failure", "message" => $message));
|
||||
}
|
||||
function printSuccess($message = "none")
|
||||
{
|
||||
echo json_encode(array("status" => "success", "message" => $message));
|
||||
}
|
||||
|
||||
function result($count)
|
||||
{
|
||||
if ($count > 0) {
|
||||
printSuccess();
|
||||
} else {
|
||||
printFailure();
|
||||
}
|
||||
}
|
||||
|
||||
function sendEmail($from,$to, $title, $body)
|
||||
{
|
||||
// Sanitize $from to prevent email header injection
|
||||
$from = str_replace(["\r", "\n", "\r\n"], '', $from);
|
||||
$header = "From: $from" . "\n" . "CC: $from";
|
||||
mail($to, $title, $body, $header);
|
||||
}
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* jwtconnect.php — Unified Authentication Gateway (بوابة المصادقة الموحدة)
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY UPGRADE: هذا الملف أصبح بوابة مصادقة إجبارية.
|
||||
* كل طلب يجب أن يمر بأحد المسارات التالية:
|
||||
*
|
||||
* Path 1: S2S API Key → X-S2S-Api-Key header
|
||||
* Path 2: Payment Key → PAYMENT_KEY header
|
||||
* Path 3: Webhook Token → X-Auth-Token header
|
||||
* Path 4: Cron Key / CLI → X-Cron-Key header أو CLI execution
|
||||
* Path 5: Nabeh API Key → X-API-Key header (server-to-server من منصة نبه)
|
||||
* Path 6: JWT (default) → Authorization: Bearer <token>
|
||||
*
|
||||
* أي طلب بدون أي مصادقة → يُرفض تلقائياً من authenticateJWT()
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Load environment variables from .env file
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-wallet/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// Get environment variables (You don't need user/pass for JWT auth itself)
|
||||
$secretKey = getenv('SECRET_KEY'); // Only need the secret key now
|
||||
|
||||
// --- CORS Headers ---
|
||||
$allowedOrigins = [
|
||||
|
||||
'https://wallet.siromove.com',
|
||||
'https://wallet-syria.siromove.com',
|
||||
'https://wallet-egypt.siromove.com',
|
||||
'https://wallet-jordan.siromove.com',
|
||||
];
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowedOrigins)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
header("Access-Control-Allow-Origin: https://walletintaleq.intaleq.xyz");
|
||||
}
|
||||
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-S2S-Api-Key, PAYMENT_KEY, X-Auth-Token, X-Cron-Key, X-HMAC-Auth, X-Device-FP, X-API-Key");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Handle preflight requests (OPTIONS)
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
|
||||
// --- Database Connection ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
// --- Load Functions ---
|
||||
include "functions.php";
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// UNIFIED AUTHENTICATION GATEWAY (بوابة المصادقة الموحدة)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
$authMethod = null;
|
||||
$decodedToken = null;
|
||||
|
||||
// --- Path 1: S2S API Key (server-to-server calls) ---
|
||||
$s2sKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
$expectedS2s = getenv('S2S_SHARED_KEY');
|
||||
|
||||
if (!empty($s2sKey) && !empty($expectedS2s) && hash_equals($expectedS2s, $s2sKey)) {
|
||||
$authMethod = 'S2S';
|
||||
}
|
||||
|
||||
// --- Path 2: Payment Key (transfer endpoint) ---
|
||||
if (!$authMethod) {
|
||||
$paymentKey = $_SERVER['HTTP_PAYMENT_KEY'] ?? '';
|
||||
$expectedPayment = getenv('PAYMENT_KEY');
|
||||
|
||||
if (!empty($paymentKey) && !empty($expectedPayment) && hash_equals($expectedPayment, $paymentKey)) {
|
||||
$authMethod = 'PAYMENT_KEY';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 3: Webhook Auth Token (MTN/Cliq external services) ---
|
||||
if (!$authMethod) {
|
||||
$webhookToken = $_SERVER['HTTP_X_AUTH_TOKEN'] ?? '';
|
||||
$expectedWebhook = getenv('WEBHOOK_AUTH_TOKEN');
|
||||
|
||||
if (!empty($expectedWebhook) && !empty($webhookToken) && hash_equals($expectedWebhook, $webhookToken)) {
|
||||
$authMethod = 'WEBHOOK';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 4: Cron Key / CLI execution ---
|
||||
if (!$authMethod) {
|
||||
// 4a: CLI execution (php script.php directly)
|
||||
if (php_sapi_name() === 'cli' || php_sapi_name() === 'cli-server') {
|
||||
$authMethod = 'CLI';
|
||||
} else {
|
||||
// 4b: HTTP cron call with key header
|
||||
$cronKey = $_SERVER['HTTP_X_CRON_KEY'] ?? '';
|
||||
$expectedCron = getenv('CRON_KEY');
|
||||
|
||||
if (!empty($cronKey) && !empty($expectedCron) && hash_equals($expectedCron, $cronKey)) {
|
||||
$authMethod = 'CRON';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 5: Nabeh API Key (server-to-server من منصة نبه) ---
|
||||
if (!$authMethod) {
|
||||
$nabehKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
|
||||
$expectedNabeh = getenv('NABEH_API_KEY');
|
||||
|
||||
if (!empty($nabehKey) && !empty($expectedNabeh) && hash_equals($expectedNabeh, $nabehKey)) {
|
||||
$authMethod = 'NABEH';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Path 6 (DEFAULT): JWT Authentication ---
|
||||
// إذا لم يتم التعرف على أي مسار آخر، يُفرض JWT.
|
||||
// authenticateJWT() ستُرجع 401 وتوقف التنفيذ إذا لم يكن هناك JWT صالح.
|
||||
if (!$authMethod) {
|
||||
$decodedToken = authenticateJWT();
|
||||
$authMethod = 'JWT';
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log($e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A database error occurred.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
function loadEnvironment($env_file = null) {
|
||||
|
||||
if ($env_file && file_exists($env_file)) {
|
||||
// use provided path
|
||||
} else {
|
||||
$env_file = '/home/intaleq-walletintaleq/env/.env';
|
||||
if (!file_exists($env_file)) {
|
||||
$env_file = __DIR__ . '/../.env';
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($env_file)) {
|
||||
error_log("❌ .env not found: $env_file");
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line) || strpos($line, '#') === 0) continue;
|
||||
$parts = explode('=', $line, 2);
|
||||
if (count($parts) === 2) {
|
||||
[$keyName, $value] = $parts;
|
||||
$value = trim($value, "'\"");
|
||||
putenv("$keyName=$value");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// loginWalletAdmin.php (Modified for Intaleq Admin Integration)
|
||||
require_once realpath(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once 'load_env.php';
|
||||
$env_file = '/home/intaleq-wallet/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
|
||||
include "functions.php";
|
||||
|
||||
// --- استدعاء المفاتيح ---
|
||||
$secretKey = getenv('SECRET_KEY');
|
||||
$allowed1 = getenv('allowedWallet1');
|
||||
$allowed2 = getenv('allowedWallet2');
|
||||
$passwordnewpassenger = getenv('passwordnewpassenger');
|
||||
$issuer = 'Tripz-Wallet';
|
||||
$allowedAudiences = array_filter([$allowed1, $allowed2]);
|
||||
|
||||
// --- إعداد رؤوس CORS ---
|
||||
header('Content-Type: application/json');
|
||||
header("Access-Control-Allow-Origin: https://walletintaleq.intaleq.xyz"); // Wallet admin only
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Device-FP");
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- التحقق من المفاتيح ---
|
||||
if (empty($secretKey) || empty($passwordnewpassenger) || empty($allowedAudiences)) {
|
||||
http_response_code(500);
|
||||
die(json_encode(['error' => 'Server configuration error']));
|
||||
}
|
||||
|
||||
try {
|
||||
$id = filterRequest('id') ?? '';
|
||||
$password = filterRequest('password') ?? '';
|
||||
$audience = filterRequest('aud') ?? '';
|
||||
$fingerPrint = filterRequest('fingerPrint');
|
||||
|
||||
if (empty($id) || empty($password) || empty($audience) || empty($fingerPrint)) {
|
||||
http_response_code(400);
|
||||
die(json_encode(['error' => 'Missing required parameters.']));
|
||||
}
|
||||
|
||||
if (!in_array($audience, $allowedAudiences)) {
|
||||
http_response_code(403);
|
||||
die(json_encode(['error' => 'Invalid audience']));
|
||||
}
|
||||
|
||||
// --- الاتصال بقاعدة البيانات ---
|
||||
$dbuser = getenv('USER');
|
||||
$dbpass = getenv('PASS');
|
||||
$dbname = getenv('dbname');
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
|
||||
$con = new PDO($dsn, $dbuser, $dbpass, [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]);
|
||||
|
||||
// --- التحقق من الهوية ---
|
||||
// تعديل: البحث باستخدام المعرف (id) أو البصمة (fingerprint)
|
||||
$stmt = $con->prepare("SELECT * FROM `adminUser` WHERE `username` = ? OR `device_number` = ? LIMIT 1");
|
||||
$stmt->execute([$id, $fingerPrint]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
// ⚠️ CRIT-01 FIX: إزالة backdoor الدخول بكلمة سر plaintext
|
||||
// لا يمكن الدخول إلا عبر مستخدمين مسجلين في قاعدة البيانات
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'User not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// التحقق من كلمة السر باستخدام password_verify (آمن)
|
||||
if (!password_verify($password, $user['password'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Invalid credentials']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إنشاء JWT ---
|
||||
$payload = [
|
||||
'user_id' => $id,
|
||||
'fingerPrint' => $fingerPrint,
|
||||
'exp' => time() + 3600, // زيادة وقت الصلاحية لـ ساعة
|
||||
'iat' => time(),
|
||||
'iss' => $issuer,
|
||||
'aud' => $audience
|
||||
];
|
||||
|
||||
$jwt = JWT::encode($payload, $secretKey, 'HS256');
|
||||
$hmac = hash_hmac('sha256', $id, getenv('SECRET_KEY_HMAC'));
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'jwt' => $jwt,
|
||||
'hmac' => $hmac,
|
||||
'expires_in' => 3600
|
||||
]);
|
||||
http_response_code(200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// HIGH-05 FIX: لا تكشف رسائل الخطأ التفصيلية
|
||||
error_log('[loginWalletAdmin] Error: ' . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'An internal error occurred. Please try again later.']);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
class GeminiAi {
|
||||
private $apiKey;
|
||||
// Updated to the requested model
|
||||
private $model = "gemini-flash-lite-latest";
|
||||
private $baseUrl = "https://generativelanguage.googleapis.com/v1beta/models/";
|
||||
|
||||
public function __construct($apiKey) {
|
||||
if (empty($apiKey)) {
|
||||
throw new Exception("Gemini API Key is missing.");
|
||||
}
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
|
||||
public function verifyPayment($invoiceNumber, $amount, $paymentMethod, $proofText, $proofImageBase64 = '') {
|
||||
$prompt = "You are a financial verifier. The user claims they transferred $amount via $paymentMethod for invoice $invoiceNumber. ";
|
||||
if (!empty($proofText)) {
|
||||
$prompt .= "Here is their proof text: '$proofText'. ";
|
||||
}
|
||||
$prompt .= "Does the provided proof clearly indicate a successful transfer of $amount? Respond ONLY with a valid JSON object: {\"verified\": true/false, \"reason\": \"your reasoning\"}.";
|
||||
|
||||
$parts = [["text" => $prompt]];
|
||||
|
||||
if (!empty($proofImageBase64)) {
|
||||
$parts[] = [
|
||||
"inline_data" => [
|
||||
"mime_type" => "image/jpeg",
|
||||
"data" => $proofImageBase64
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$reqData = [
|
||||
"contents" => [
|
||||
[
|
||||
"parts" => $parts
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$url = $this->baseUrl . $this->model . ":generateContent?key=" . $this->apiKey;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($reqData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
error_log("Gemini API Error: " . $response);
|
||||
throw new Exception("AI Verification service unavailable. Details: " . $response);
|
||||
}
|
||||
|
||||
$resData = json_decode($response, true);
|
||||
$aiText = $resData['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
||||
|
||||
// Clean AI Text (remove markdown json block if exists)
|
||||
$aiText = preg_replace('/```json|```/', '', $aiText);
|
||||
$aiResult = json_decode(trim($aiText), true);
|
||||
|
||||
if ($aiResult && isset($aiResult['verified'])) {
|
||||
return $aiResult;
|
||||
}
|
||||
|
||||
throw new Exception("Invalid response format from Gemini: " . $aiText);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
include "../../connect.php";
|
||||
|
||||
// Load the .env file and set environment variables
|
||||
$env_file = __DIR__ . '/../../.env'; // Ensure the .env file exists and is named correctly
|
||||
if (file_exists($env_file)) {
|
||||
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue; // Skip comments
|
||||
}
|
||||
putenv(trim($line));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the specific key name from the request
|
||||
$keyName = filterRequest('keyName');
|
||||
|
||||
// Fetch the specific environment variable
|
||||
$envValue = getenv($keyName);
|
||||
|
||||
// Include the specific environment key in the output
|
||||
$output = [];
|
||||
if ($keyName && $envValue !== false) {
|
||||
$output[$keyName] = $envValue;
|
||||
printSuccess($output);
|
||||
} else {
|
||||
$apiKeys = getApiKeys($con);
|
||||
if ($apiKeys) {
|
||||
printSuccess($apiKeys);
|
||||
} else {
|
||||
printFailure("No records found or invalid key name");
|
||||
}
|
||||
}
|
||||
|
||||
function getApiKeys($con) {
|
||||
$sql = "SELECT `id`, `name`, `hashed_key` FROM `api_keys`";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -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."]);
|
||||
}
|
||||
?>
|
||||
@@ -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."]);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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");
|
||||
$phone = filterRequest("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,
|
||||
phone = :phone,
|
||||
cliq_phone = :cliq_phone,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$upd->execute([
|
||||
':amount' => $amount,
|
||||
':phone' => $phone ?: null,
|
||||
':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, phone, amount, cliq_phone, status, created_at, updated_at)
|
||||
VALUES
|
||||
(:invoice_number, :user_id, :user_type, :phone, :amount, :cliq_phone, 'pending', NOW(), NOW())
|
||||
");
|
||||
$ins->execute([
|
||||
':invoice_number' => $invoiceNumber,
|
||||
':user_id' => $userId,
|
||||
':user_type' => $userType,
|
||||
':phone' => $phone ?: null,
|
||||
':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."]);
|
||||
}
|
||||
?>
|
||||
+106
@@ -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 `cliq_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' => 'Finalization failed'];
|
||||
}
|
||||
}
|
||||
|
||||
// --- دوال مساعدة خاصة بالسائق ---
|
||||
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.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod) VALUES (:driverId, 'driver', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':driverId' => $driverId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet 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.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (passengerId, driverId, amount, paymentMethod) VALUES (:passengerId, 'passenger', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':passengerId' => $passengerId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet 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;
|
||||
}
|
||||
?>
|
||||
@@ -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);
|
||||
}
|
||||
?>
|
||||
@@ -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 occurred."]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
//ridedriverPayment/add.php
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("payment_method");
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "INSERT INTO `paymentsDriverPoints` (`amount`, `payment_method`, `driverID`)
|
||||
VALUES ('$amount', '$paymentMethod', '$driverID')";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
|
||||
$insertedID = $con->lastInsertId(); // Get the last inserted ID
|
||||
printSuccess($message = $insertedID);
|
||||
} else {
|
||||
$response = array(
|
||||
"success" => false,
|
||||
"message" => "Failed to save payment data"
|
||||
);
|
||||
echo json_encode($response);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
$sql = "DELETE FROM `paymentsDriverPoints` WHERE `id` = '$id'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
echo "Record deleted successfully";
|
||||
} else {
|
||||
// Print a failure message
|
||||
echo "Failed to delete the record";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$sql = "SELECT `id`, `amount`, `payment_method`, `driverID`, `created_at`, `updated_at`
|
||||
FROM `paymentsDriverPoints`";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
} else {
|
||||
// No records found
|
||||
echo "No records found.";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "UPDATE `paymentsDriverPoints` SET `amount` = '$amount', `payment_method` = '$paymentMethod',
|
||||
`driverID` = '$driverID' WHERE `id` = '$id'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
echo "Record updated successfully";
|
||||
} else {
|
||||
// Print a failure message
|
||||
echo "Failed to update the record";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* driverWallet/add.php — إضافة رصيد لمحفظة السائق
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY FIX:
|
||||
* - إضافة التحقق من ملكية الحساب (driverID == JWT user_id)
|
||||
* - لف العملية في Transaction ذرية
|
||||
* - استخدام FOR UPDATE لمنع Race Condition على التوكن
|
||||
* - التحقق من صحة المبلغ
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Include the database connection file (calls authenticateJWT)
|
||||
include "../../connect.php";
|
||||
|
||||
// ── 1. استخراج user_id من JWT المصادق ──────────────────────────
|
||||
$jwtUserId = $decodedToken->user_id ?? $decodedToken->sub ?? null;
|
||||
|
||||
// ── 2. استخراج والتحقق من البيانات ──────────────────────────
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$token = filterRequest("token");
|
||||
|
||||
if (empty($driverID) || !isset($amount) || empty($paymentMethod) || empty($token)) {
|
||||
printFailure("Missing required parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. التحقق من ملكية الحساب ────────────────────────────────
|
||||
// السائق المصادق يمكنه فقط إضافة رصيد لمحفظته الشخصية
|
||||
if ($jwtUserId !== null && $driverID !== $jwtUserId) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Ownership mismatch in add.php | jwt_user=%s | requested_driverID=%s | IP=%s',
|
||||
$jwtUserId, $driverID, $_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
printFailure("Forbidden: You can only modify your own wallet");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. التحقق من المبلغ ─────────────────────────────────────
|
||||
$amount = floatval($amount);
|
||||
if ($amount <= 0 || $amount > 1000000) {
|
||||
printFailure("Invalid amount");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 5. العملية الذرية ─────────────────────────────────────────
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// التحقق من التوكن مع قفل السجل (FOR UPDATE) لمنع Race Condition
|
||||
$stmt = $con->prepare("SELECT * FROM payment_tokens WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||
$stmt->execute([':token' => $token]);
|
||||
$tokenData = $stmt->fetch();
|
||||
|
||||
if ($tokenData) {
|
||||
// إدخال سجل المحفظة
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// تحديث حالة التوكن
|
||||
$stmt = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE id = :tokenID");
|
||||
$stmt->execute([':tokenID' => $tokenData['id']]);
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Invalid or already used token");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[driverWallet/add] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// Include the database connection file
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
//add300ToDriver.php
|
||||
|
||||
// Get the request parameters
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 1) ATOMIC CHECK + INSERT TO PREVENT RACE CONDITION
|
||||
// -------------------------------------------------------------
|
||||
$con->beginTransaction();
|
||||
|
||||
$check = $con->prepare("
|
||||
SELECT id
|
||||
FROM driverWallet
|
||||
WHERE driverID = :driverID AND paymentMethod = :paymentMethod
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
");
|
||||
|
||||
$check->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($check->rowCount() > 0) {
|
||||
$con->rollBack();
|
||||
printFailure("لقد تم منح هذا الدفع للسائق مسبقاً — لا يمكن تكراره.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 2) INSERT INTO driverWallet
|
||||
// -------------------------------------------------------------
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute(array(
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
));
|
||||
|
||||
$con->commit();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
|
||||
printSuccess("Record saved successfully");
|
||||
|
||||
// Notify driver
|
||||
$messageBody = "تم إضافة رصيد بقيمة $amount إلى محفظتك بنجاح.";
|
||||
// sendWhatsAppFromServer($phone, $messageBody);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// 3) INSERT 30,000 POINTS FOR NEW DRIVER
|
||||
// -------------------------------------------------------------
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints`
|
||||
(`amount`, `payment_method`, `driverID`, `created_at`, `updated_at`)
|
||||
VALUES (:amount, :method, :driverID, NOW(), NOW())";
|
||||
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute(array(
|
||||
':amount' => 300,
|
||||
':method' => $paymentMethod,
|
||||
':driverID' => $driverID
|
||||
));
|
||||
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* addFromAdmin.php — إضافة رصيد من المسؤول (Admin Only)
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
* SECURITY FIX: كان هذا الملف بدون أي مصادقة! ❌
|
||||
* الآن يتطلب:
|
||||
* 1. JWT مصادق (عبر authenticateJWT)
|
||||
* 2. التحقق من دور المسؤول (admin role)
|
||||
* 3. التحقق من المبلغ (حد أقصى)
|
||||
* 4. تسجيل تدقيق لكل عملية (audit log)
|
||||
* ═══════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
// Include the database connection WITH JWT authentication
|
||||
include "../../connect.php";
|
||||
// connect.php calls authenticateJWT() → 5-layer security check ✅
|
||||
|
||||
// ── 1. استخراج user_id من JWT المصادق ──────────────────────────
|
||||
$adminUserId = $decodedToken->user_id ?? $decodedToken->sub ?? null;
|
||||
$adminRole = $decodedToken->role ?? null;
|
||||
|
||||
// ── 2. التحقق من صلاحيات المسؤول ────────────────────────────
|
||||
// فقط المسؤول يمكنه إضافة رصيد يدوياً
|
||||
if ($adminRole !== 'admin' && $adminRole !== 'super_admin') {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Non-admin attempted addFromAdmin | user=%s | role=%s | IP=%s',
|
||||
$adminUserId,
|
||||
$adminRole ?? '(null)',
|
||||
$_SERVER['REMOTE_ADDR'] ?? 'unknown'
|
||||
));
|
||||
http_response_code(403);
|
||||
printFailure("Forbidden: Admin access required");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 3. استخراج والتحقق من البيانات ──────────────────────────
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
if (empty($driverID) || !isset($amount) || empty($paymentMethod)) {
|
||||
printFailure("Missing required parameters: driverID, amount, paymentMethod");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 4. التحقق من المبلغ ─────────────────────────────────────
|
||||
$amount = floatval($amount);
|
||||
if ($amount <= 0 || $amount > 1000000) {
|
||||
error_log(sprintf(
|
||||
'⚠️ [SECURITY] Invalid amount in addFromAdmin | admin=%s | amount=%s | driverID=%s',
|
||||
$adminUserId, $amount, $driverID
|
||||
));
|
||||
printFailure("Invalid amount: must be between 0 and 1,000,000");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── 5. العملية الذرية ─────────────────────────────────────────
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// إدخال سجل المحفظة
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID ?? ('admin_' . time() . '_' . bin2hex(random_bytes(4))),
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// ── 6. تسجيل تدقيق (Audit Log) ─────────────────────────
|
||||
$auditStmt = $con->prepare(
|
||||
"INSERT INTO `admin_audit_log` (`admin_id`, `action`, `table_name`, `record_id`, `details`)
|
||||
VALUES (:admin_id, :action, :table_name, :record_id, :details)"
|
||||
);
|
||||
$auditStmt->execute([
|
||||
':admin_id' => $adminUserId,
|
||||
':action' => 'addFromAdmin_wallet',
|
||||
':table_name' => 'driverWallet',
|
||||
':record_id' => $driverID,
|
||||
':details' => json_encode([
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'phone' => $phone,
|
||||
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
], JSON_UNESCAPED_UNICODE)
|
||||
]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
printSuccess("Record saved successfully");
|
||||
|
||||
// إرسال إشعار واتساب للسائق
|
||||
if (!empty($phone)) {
|
||||
$messageBody = "تم إضافة رصيد بقيمة $amount إلى محفظتك بنجاح.";
|
||||
sendWhatsAppFromServer($phone, $messageBody);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[addFromAdmin] Error: " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
//addPaymentToken.php
|
||||
$driverID = filterRequest("driverID");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
// Check if required fields are present
|
||||
if ($driverID === null || $amount === null) {
|
||||
printFailure("Missing required fields: driverID and amount must be provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate a more secure token
|
||||
$token = generateSecureToken($driverID, $amount);
|
||||
|
||||
// Store the token in the database
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (?, ?, NOW(), ?)");
|
||||
|
||||
try {
|
||||
$stmt->execute([$token, $driverID, $amount]);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
printSuccess($token);
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("[addPaymentToken] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
|
||||
function generateSecureToken($driverID, $amount) {
|
||||
global $secretKey;
|
||||
// Concatenate the parameters
|
||||
$data = $driverID . $amount . time();
|
||||
|
||||
// Add the secret key from the environment variable
|
||||
$data .= $secretKey;
|
||||
|
||||
// Generate a hash
|
||||
$hash = hash('sha256', $data);
|
||||
|
||||
// Add some randomness
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
|
||||
// Combine hash and random bytes
|
||||
$token = $hash . $randomBytes;
|
||||
|
||||
// Truncate to a reasonable length (e.g., 64 characters)
|
||||
return substr($token, 0, 64);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* add_s2s_reward.php — Payment Server Endpoint
|
||||
*
|
||||
* Inserts wallet credit/debit records into driverWallet.
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverID = filterRequest("driverID");
|
||||
$paymentID = filterRequest("paymentID");
|
||||
$amount = filterRequest("amount");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
$points = filterRequest("points"); // Optional raw points
|
||||
|
||||
if (empty($driverID) || empty($paymentID) || !isset($amount) || empty($paymentMethod)) {
|
||||
printFailure("Missing required parameters: driverID, paymentID, amount, paymentMethod");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// Prevent duplicate challenge claims using paymentsDriverPoints table
|
||||
if (strpos($paymentMethod, 'daily_') === 0 || strpos($paymentMethod, 'weekly_') === 0) {
|
||||
$checkSql = "SELECT id FROM paymentsDriverPoints WHERE driverID = :driver_id AND payment_method = :challenge_id AND DATE(created_at) = CURDATE() FOR UPDATE";
|
||||
$stmtCheck = $con->prepare($checkSql);
|
||||
$stmtCheck->execute([
|
||||
':driver_id' => $driverID,
|
||||
':challenge_id' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmtCheck->rowCount() > 0) {
|
||||
$con->rollBack();
|
||||
printFailure("Reward already claimed today");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO `driverWallet` (
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`amount`,
|
||||
`paymentMethod`
|
||||
) VALUES (
|
||||
:driverID,
|
||||
:paymentID,
|
||||
:amount,
|
||||
:paymentMethod
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $amount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// If points are provided, also insert into paymentsDriverPoints
|
||||
if (!empty($points)) {
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints` (
|
||||
`amount`,
|
||||
`payment_method`,
|
||||
`driverID`
|
||||
) VALUES (
|
||||
:points,
|
||||
:paymentMethod,
|
||||
:driverID
|
||||
);";
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute([
|
||||
':points' => $points,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':driverID' => $driverID
|
||||
]);
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("add_s2s_reward: " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
include '../../connect.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$driverID = filterRequest('driverID');
|
||||
$amount = floatval(filterRequest('amount'));
|
||||
|
||||
if (empty($driverID) || empty($amount) || $amount <= 0) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Missing required fields or invalid amount']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// 1. Fetch current budget
|
||||
$stmt = $con->prepare("SELECT SUM(amount) as diff FROM payments WHERE captain_id = :driverID FOR UPDATE");
|
||||
$stmt->execute([':driverID' => $driverID]);
|
||||
$sumRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$totalBudget = floatval($sumRow['diff']);
|
||||
|
||||
if ($totalBudget < $amount) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Insufficient budget']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Generate unique tokens
|
||||
$paymentID1 = "budget2pt_" . time() . bin2hex(random_bytes(4));
|
||||
$paymentID2 = "pt2budget_" . time() . bin2hex(random_bytes(4));
|
||||
$token1 = bin2hex(random_bytes(32));
|
||||
$token2 = bin2hex(random_bytes(32));
|
||||
|
||||
// 3. Deduct from budget (payments)
|
||||
$deductAmount = -$amount;
|
||||
$stmt = $con->prepare("INSERT INTO payments (captain_id, amount, rideId, payment_method, passengerID, token)
|
||||
VALUES (:driverID, :amount, :rideId, 'myBudget', 'myBudgetToPoint', :token)");
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':amount' => $deductAmount,
|
||||
':rideId' => $paymentID1,
|
||||
':token' => $token1
|
||||
]);
|
||||
|
||||
// 4. Add to points (paymentsDriverPoints)
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (captain_id, paymentID, amount, token, paymentMethod)
|
||||
VALUES (:driverID, :paymentID, :amount, :token, 'fromBudget')");
|
||||
$stmt->execute([
|
||||
':driverID' => $driverID,
|
||||
':paymentID' => $paymentID2,
|
||||
':amount' => $amount,
|
||||
':token' => $token2
|
||||
]);
|
||||
|
||||
// Commit Transaction
|
||||
$con->commit();
|
||||
|
||||
echo json_encode(['status' => 'success', 'message' => 'Budget converted to points successfully']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log('[convertBudgetToPoints] Error: ' . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred.']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
// ==========================================
|
||||
// Cron Job: Remove Duplicate Records Daily
|
||||
// Tables: driverWallet, paymentsDriverPoints
|
||||
// ==========================================
|
||||
|
||||
// Load DB Connection
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
// Function to run cleanup query
|
||||
function runCleanup($con, $deleteQuery, $tableName) {
|
||||
try {
|
||||
$stmt = $con->prepare($deleteQuery);
|
||||
$stmt->execute();
|
||||
|
||||
echo "Cleanup completed for table: $tableName\n";
|
||||
echo "Rows affected: " . $stmt->rowCount() . "\n\n";
|
||||
} catch (Exception $e) {
|
||||
echo "Error cleaning $tableName\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DELETE DUPLICATES FOR driverWallet
|
||||
// ==========================================
|
||||
|
||||
$deleteDriverWallet = "
|
||||
DELETE FROM driverWallet
|
||||
WHERE id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER(PARTITION BY driverID ORDER BY dateCreated DESC) AS rn
|
||||
FROM driverWallet
|
||||
) AS subquery
|
||||
WHERE rn = 1
|
||||
);";
|
||||
|
||||
runCleanup($con, $deleteDriverWallet, "driverWallet");
|
||||
|
||||
|
||||
// ==========================================
|
||||
// DELETE DUPLICATES FOR paymentsDriverPoints
|
||||
// ==========================================
|
||||
|
||||
$deletePaymentsPoints = "
|
||||
DELETE FROM paymentsDriverPoints
|
||||
WHERE id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT
|
||||
id,
|
||||
ROW_NUMBER() OVER(PARTITION BY driverID ORDER BY created_at DESC) AS rn
|
||||
FROM paymentsDriverPoints
|
||||
) AS subquery
|
||||
WHERE rn = 1
|
||||
);";
|
||||
|
||||
runCleanup($con, $deletePaymentsPoints, "paymentsDriverPoints");
|
||||
|
||||
echo "Cron job completed successfully.\n";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
YEAR(`driver_orders`.`created_at`) AS `year`,
|
||||
MONTH(`driver_orders`.`created_at`) AS `month`,
|
||||
COUNT(*) AS `total_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Finished' THEN 1 ELSE 0 END) AS `completed_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Apply' THEN 1 ELSE 0 END) AS `pending_orders`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Cancel' THEN 1 ELSE 0 END) AS `canceled_orders`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Finished' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_completed`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Apply' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_pending`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Cancel' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_canceled`,
|
||||
SUM(CASE WHEN `ride`.`status` = 'Refused' THEN 1 ELSE 0 END) AS `rejected_orders`,
|
||||
ROUND(SUM(CASE WHEN `ride`.`status` = 'Refused' THEN 1 ELSE 0 END) / COUNT(*) * 100, 2) AS `percent_rejected`
|
||||
FROM
|
||||
`driver_orders`
|
||||
LEFT JOIN `ride` ON `ride`.`id` = `driver_orders`.`order_id`
|
||||
WHERE
|
||||
`driver_orders`.`driver_id` = '$driverID'
|
||||
AND YEAR(`driver_orders`.`created_at`) = YEAR(CURDATE())
|
||||
AND MONTH(`driver_orders`.`created_at`) = MONTH(CURDATE())
|
||||
GROUP BY
|
||||
YEAR(`driver_orders`.`created_at`),
|
||||
MONTH(`driver_orders`.`created_at`)
|
||||
ORDER BY
|
||||
`year`,
|
||||
`month`;
|
||||
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
COALESCE(dw.id, 0) AS id,
|
||||
COALESCE(dw.driverID, '0') AS driverID,
|
||||
COALESCE(dw.paymentID, '0') AS paymentID,
|
||||
COALESCE(dw.dateCreated, '1970-01-01 00:00:00') AS dateCreated,
|
||||
COALESCE(dw.amount, 0) AS amount,
|
||||
COALESCE(dw.paymentMethod, '0') AS paymentMethod,
|
||||
COALESCE(dw.dateUpdated, '1970-01-01 00:00:00') AS dateUpdated,
|
||||
COALESCE((SELECT SUM(amount) FROM driverWallet WHERE driverID = '$driverID'), 0) AS total_amount
|
||||
FROM
|
||||
driverWallet dw
|
||||
WHERE
|
||||
dw.driverID = '$driverID'
|
||||
GROUP BY
|
||||
dw.id,
|
||||
dw.driverID,
|
||||
dw.paymentID,
|
||||
dw.dateCreated,
|
||||
dw.amount,
|
||||
dw.paymentMethod,
|
||||
dw.dateUpdated
|
||||
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$driver_phone = filterRequest("driver_phone");
|
||||
|
||||
$sql = "SELECT
|
||||
`driverToken`.`token`,
|
||||
`driver`.`id`,
|
||||
`driver`.`phone`,
|
||||
`driver`.`name_arabic`as name,
|
||||
driver.national_number
|
||||
FROM
|
||||
`driverToken`
|
||||
LEFT JOIN `driver` ON `driver`.`id` = `driverToken`.`captain_id`
|
||||
WHERE
|
||||
`driver`.`phone` = '$driver_phone'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($data) {
|
||||
// Print the car location data as JSON
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
|
||||
'data' => $data
|
||||
]);
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "No car locations found");
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
`id`,
|
||||
`driverID`,
|
||||
`paymentID`,
|
||||
`dateCreated`,
|
||||
`amount`,
|
||||
`paymentMethod`,
|
||||
`dateUpdated`,
|
||||
(SELECT SUM(`amount`)
|
||||
FROM `driverWallet`
|
||||
WHERE `driverID` = '$driverID'
|
||||
AND `dateCreated` >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
|
||||
) AS totalAmount
|
||||
FROM `driverWallet`
|
||||
WHERE `driverID` = '$driverID'
|
||||
AND `dateCreated` >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
|
||||
ORDER BY `dateCreated` DESC;
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
$sql = "SELECT
|
||||
driverWallet.`id`,
|
||||
driverWallet.amount,
|
||||
driverWallet.dateCreated as created_at
|
||||
FROM
|
||||
`driverWallet`
|
||||
WHERE
|
||||
driverWallet.driverID = '$driverID' AND driverWallet.dateCreated >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
|
||||
ORDER BY
|
||||
`driverWallet`.`id`
|
||||
DESC";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* get_s2s_wallet_dashboard.php — Payment Server Endpoint
|
||||
*
|
||||
* Returns wallet metrics (challenge points, today's earnings) for a driver.
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
if (empty($driverID)) {
|
||||
printFailure("Missing required parameter: driverID");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Calculate Sum of claimed challenge points
|
||||
$stmtChallengePoints = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM `paymentsDriverPoints`
|
||||
WHERE driverID = :driver_id
|
||||
AND (payment_method LIKE 'daily_%' OR payment_method LIKE 'weekly_%')
|
||||
");
|
||||
$stmtChallengePoints->execute([':driver_id' => $driverID]);
|
||||
$challengePoints = (int)($stmtChallengePoints->fetchColumn() ?: 0);
|
||||
|
||||
// 2. Calculate Today's Earnings
|
||||
$stmtTodayEarnings = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM payments
|
||||
WHERE driverID = :driver_id
|
||||
AND DATE(created_at) = CURDATE()
|
||||
");
|
||||
$stmtTodayEarnings->execute([':driver_id' => $driverID]);
|
||||
$todayEarnings = (float)($stmtTodayEarnings->fetchColumn() ?: 0.0);
|
||||
|
||||
// 3. Calculate Total Wallet Balance
|
||||
$stmtTotalWallet = $con->prepare("
|
||||
SELECT COALESCE(SUM(amount), 0)
|
||||
FROM `driverWallet`
|
||||
WHERE driverID = :driver_id
|
||||
");
|
||||
$stmtTotalWallet->execute([':driver_id' => $driverID]);
|
||||
$totalWallet = (float)($stmtTotalWallet->fetchColumn() ?: 0.0);
|
||||
|
||||
printSuccess([
|
||||
"challengePoints" => $challengePoints,
|
||||
"todayEarnings" => $todayEarnings,
|
||||
"totalWallet" => $totalWallet
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("[get_s2s_wallet_dashboard] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
// Include the database connection file
|
||||
include "../../connect.php";
|
||||
|
||||
// Get the request parameters
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$payment_amount = filterRequest("payment_amount");
|
||||
$timePromo = filterRequest("timePromo"); // Example: 'morning' or 'afternoon'
|
||||
//$createdAt = date("Y-m-d H:i:s"); // Get the current date and time
|
||||
$currentDate = date("Y-m-d"); // Current date for comparison
|
||||
|
||||
// Check if a promotion record for the same driver already exists today
|
||||
$sqlCheck = "SELECT COUNT(*) FROM `driver_promotions` WHERE `driver_id` = :driver_id AND DATE(`created_at`) = :current_date
|
||||
and timePromo=:timePromo
|
||||
";
|
||||
$stmtCheck = $con->prepare($sqlCheck);
|
||||
$stmtCheck->execute(array(
|
||||
':driver_id' => $driver_id,
|
||||
':current_date' => $currentDate
|
||||
':timePromo' =>$timePromo
|
||||
));
|
||||
|
||||
$count = $stmtCheck->fetchColumn();
|
||||
|
||||
if ($count > 0) {
|
||||
// A record exists for today, so prevent the insertion
|
||||
printFailure("A promotion record for this driver already exists for today.");
|
||||
} else {
|
||||
// No record exists for today, so insert the new promotion
|
||||
$sqlInsert = "INSERT INTO `driver_promotions` (
|
||||
`driver_id`,
|
||||
`payment_amount`,
|
||||
`timePromo`
|
||||
) VALUES (
|
||||
:driver_id,
|
||||
:payment_amount,
|
||||
:timePromo
|
||||
);";
|
||||
|
||||
// Prepare the insert statement
|
||||
$stmtInsert = $con->prepare($sqlInsert);
|
||||
$stmtInsert->execute(array(
|
||||
':driver_id' => $driver_id,
|
||||
':payment_amount' => $payment_amount,
|
||||
':timePromo' => $timePromo,
|
||||
':createdAt' => $createdAt
|
||||
));
|
||||
|
||||
// Check if the query was successful
|
||||
if ($stmtInsert->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess("Promotion record saved successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure("Failed to save promotion record");
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
// Connect to database
|
||||
include '../../connect.php';
|
||||
|
||||
// Get trip details
|
||||
$driverName = filterRequest('name');
|
||||
$driverEmail = filterRequest('email');
|
||||
$driverPhone = filterRequest('phone');
|
||||
$amount = filterRequest('amount');
|
||||
$newDriverName = filterRequest('newDriver');
|
||||
$newEmail=filterRequest('newEmail');
|
||||
|
||||
// Get language preference from database or user input
|
||||
$language = 'en'; // Default to English
|
||||
// Email content
|
||||
if ($language === 'ar') {
|
||||
$bodyEmail = "<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f8fa;
|
||||
color: #14171a;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #1da1f2;
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
a {
|
||||
color: #1da1f2;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<h1>تفاصيل نقلك على سفر</h1>
|
||||
<p>شكراً لاستخدام خدمتنا. نتمنى لك يوماً رائعاً!</p>
|
||||
<p>نريد إعلامك أن مبلغ $amount تم نقله من حسابك إلى السائق الجديد، $newDriverName (هاتف: $driverPhone).</p>
|
||||
<p>مع خالص التحية،<br> فريق سفر</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
} else {
|
||||
$bodyEmail = "<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f8fa;
|
||||
color: #14171a;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #1da1f2;
|
||||
margin-top: 0;
|
||||
}
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
a {
|
||||
color: #1da1f2;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='container'>
|
||||
<img src='https://lh3.googleusercontent.com/a/ACg8ocLe5TgvmTjoFx7KjIoWGxX0G2ryKBTzUZi2-mBYb9DI1dsKQ0WEYh5ZPdnA3WeFbp9VnaTNzJuA0w8S4RiQ7042AKrOwXo3=s576-c-no' alt='SIRO App Logo' style='width: 150px; margin: 20px auto; display: block;'>
|
||||
|
||||
<h1>Your SIRO Transfer Details</h1>
|
||||
<p>Thank you for using our service. We hope you have a great day!</p>
|
||||
<p>We want to inform you that an amount of $amount has been transferred from your account to the new driver: $newDriverName (Phone: $driverPhone).</p>
|
||||
<p>Regards,<br> SIRO Team</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
}
|
||||
|
||||
// Email headers
|
||||
$supportEmail = 'siroteam@siro.live';
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||
$headers .= "From: $supportEmail\r\n";
|
||||
|
||||
// Send email
|
||||
if (!empty($driverEmail)) {
|
||||
if (mail($driverEmail, "Your SIRO Transfer Details", $bodyEmail, $headers)) {
|
||||
|
||||
mail($newEmail, "Your SIRO Transfer Details", $bodyEmail, $headers);
|
||||
echo "Email sent successfully.";
|
||||
} else {
|
||||
echo "Email sending failed.";
|
||||
}
|
||||
} else {
|
||||
echo "Invalid email address: $driverEmail";
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
include '../../jwtconnect.php';
|
||||
|
||||
// Disable error reporting output for production API
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
|
||||
// Set header
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$senderID = filterRequest('senderID');
|
||||
$receiverID = filterRequest('receiverID'); // Now receiving the ID directly from Main Server
|
||||
$amount = floatval(filterRequest('amount'));
|
||||
$country = filterRequest('country'); // e.g. Egypt, Syria, Jordan
|
||||
|
||||
if (empty($senderID) || empty($receiverID) || empty($amount) || empty($country)) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Missing required fields']);
|
||||
exit;
|
||||
}
|
||||
// --- Payment Key Authentication ---
|
||||
$expectedKey = getenv('PAYMENT_KEY');
|
||||
$providedKey = $_SERVER['HTTP_PAYMENT_KEY'] ?? '';
|
||||
|
||||
if (empty($expectedKey) || $providedKey !== $expectedKey) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Unauthorized Payment Server Access (Invalid Key)']);
|
||||
exit;
|
||||
}
|
||||
// 1. Determine Fee based on Country
|
||||
$fee = 0;
|
||||
if (strtolower($country) === 'egypt') {
|
||||
$fee = 5;
|
||||
if ($amount < 10) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Egypt is 10']);
|
||||
exit;
|
||||
}
|
||||
} elseif (strtolower($country) === 'syria') {
|
||||
$fee = 10;
|
||||
if ($amount < 100) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Syria is 100']);
|
||||
exit;
|
||||
}
|
||||
} elseif (strtolower($country) === 'jordan') {
|
||||
$fee = 0.25;
|
||||
if ($amount < 1) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'Minimum transfer amount in Jordan is 1']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Default fee if unknown
|
||||
$fee = 5;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
if ($receiverID == $senderID) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Cannot transfer to yourself']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Fetch Sender Budget (with FOR UPDATE to lock rows)
|
||||
$stmt = $con->prepare("SELECT SUM(amount) as diff FROM payments WHERE captain_id = :senderID FOR UPDATE");
|
||||
$stmt->execute([':senderID' => $senderID]);
|
||||
$sumRow = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$totalBudget = floatval($sumRow['diff']);
|
||||
|
||||
if ($totalBudget < $amount) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Insufficient budget']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$amountForReceiver = $amount - $fee;
|
||||
if ($amountForReceiver <= 0) {
|
||||
$con->rollBack();
|
||||
echo json_encode(['status' => 'error', 'message' => 'Transfer amount must be greater than the fee']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Generate unique Tokens and paymentIDs
|
||||
$paymentID1 = "transfer_" . time() . bin2hex(random_bytes(4));
|
||||
$paymentID2 = "transfer_recv_" . time() . bin2hex(random_bytes(4));
|
||||
$token1 = bin2hex(random_bytes(32));
|
||||
$token2 = bin2hex(random_bytes(32));
|
||||
$siroToken = bin2hex(random_bytes(32));
|
||||
|
||||
// 4. Deduct from Sender (payments table)
|
||||
$deductAmount = -$amount;
|
||||
$stmt = $con->prepare("INSERT INTO payments (captain_id, amount, rideId, payment_method, passengerID, token)
|
||||
VALUES (:senderID, :amount, :rideId, 'cash_transfer', :receiverRef, :token)");
|
||||
$stmt->execute([
|
||||
':senderID' => $senderID,
|
||||
':amount' => $deductAmount,
|
||||
':rideId' => $paymentID1,
|
||||
':receiverRef' => 'To ' . $receiverID,
|
||||
':token' => $token1
|
||||
]);
|
||||
|
||||
// 5. Add to Receiver Points (paymentsDriverPoints table)
|
||||
$stmt = $con->prepare("INSERT INTO paymentsDriverPoints (captain_id, paymentID, amount, token, paymentMethod)
|
||||
VALUES (:receiverID, :paymentID, :amount, :token, 'Transfer')");
|
||||
$stmt->execute([
|
||||
':receiverID' => $receiverID,
|
||||
':paymentID' => $paymentID2,
|
||||
':amount' => $amountForReceiver,
|
||||
':token' => $token2
|
||||
]);
|
||||
|
||||
// 6. Add Fee to Siro Wallet
|
||||
$stmt = $con->prepare("INSERT INTO siroWallet (amount, paymentMethod, passengerId, token, driverId)
|
||||
VALUES (:fee, 'payout fee', 'driver', :token, :senderID)");
|
||||
$stmt->execute([
|
||||
':fee' => $fee,
|
||||
':token' => $siroToken,
|
||||
':senderID' => $senderID
|
||||
]);
|
||||
|
||||
// Commit Transaction
|
||||
$con->commit();
|
||||
|
||||
echo json_encode(['status' => 'success', 'message' => 'Transfer completed successfully on payment server']);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$con->rollBack();
|
||||
error_log('[transfer] Error: ' . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred.']);
|
||||
}
|
||||
?>
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
// هذا الملف هو نقطة النهاية بعد الدفع، ويقوم بكل عمليات التحقق وإضافة الرصيد
|
||||
// This file is the final endpoint after payment, handling all verification and balance updates.
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// -------------------------------------------------
|
||||
// دوال مساعدة لإنشاء التوكنات ومعرفات الدفع
|
||||
// Helper functions for creating tokens and payment IDs
|
||||
// -------------------------------------------------
|
||||
|
||||
/**
|
||||
* إنشاء توكن فريد لعملية المحفظة وتخزينه في قاعدة البيانات
|
||||
* Creates a unique token for a wallet transaction and stores it in the database.
|
||||
*/
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride"); // تأكد من صحة هذا الرابط
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string
|
||||
{
|
||||
global $secretKey; // يفترض أن هذا المتغير متاح من ملف الاتصال
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* تسجيل دفعة في جدول النقاط وإعادة المعرف الخاص بها
|
||||
* Logs a payment in the points table and returns its ID.
|
||||
*/
|
||||
function generatePaymentID($con, $driverId, $amount, $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;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// المنطق الرئيسي للمعالجة
|
||||
// Main processing logic
|
||||
// -------------------------------------------------
|
||||
|
||||
// 1. استقبال الرقم المرجعي من الرابط
|
||||
// 1. Receive the order reference from the URL.
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
echo "<h1>خطأ: الرقم المرجعي للطلب مفقود.</h1>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. الانتظار والتأكد من وصول الـ Webhook
|
||||
// 2. Wait and verify that the webhook has updated the status.
|
||||
$payment = null;
|
||||
$max_attempts = 5; // محاولة لمدة 10 ثوانٍ - Poll for 10 seconds
|
||||
for ($attempts = 0; $attempts < $max_attempts; $attempts++) {
|
||||
// تأكد من أن اسم الجدول صحيح
|
||||
// Make sure the table name is correct.
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($payment) {
|
||||
break; // تم العثور على الدفعة الناجحة - Successful payment found
|
||||
}
|
||||
sleep(2); // الانتظار لمدة ثانيتين قبل المحاولة التالية - Wait 2 seconds before retrying
|
||||
}
|
||||
|
||||
// 3. التحقق من نتيجة البحث
|
||||
// 3. Check the polling result.
|
||||
if (!$payment) {
|
||||
echo "<h1>خطأ في تأكيد الدفع</h1><p>لم نتمكن من تأكيد دفعتك. قد تستغرق العملية بضع لحظات. يرجى التحقق من رصيدك في التطبيق لاحقاً أو التواصل مع الدعم الفني.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. Atomic status claim + wallet update (prevents double-processing)
|
||||
// 4. معالجة ذرية لمنح الرصيد — تمنع التكرار في حال التزامن
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
80 => 80.0,
|
||||
200 => 215.0,
|
||||
400 => 450.0,
|
||||
1000 => 1140.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// بدء معاملة: تحديث الحالة claim + إضافة المحافظ
|
||||
$con->beginTransaction();
|
||||
|
||||
// محاولة ذرية لـ claim المعاملة (فقط إذا كانت لا تزال status = 1)
|
||||
$claimStmt = $con->prepare("UPDATE paymentsLogSyriaDriver SET status = 2 WHERE order_ref = :ref AND status = 1");
|
||||
$claimStmt->execute([':ref' => $orderRef]);
|
||||
if ($claimStmt->rowCount() === 0) {
|
||||
$con->rollBack();
|
||||
error_log("VERIFY_RACE: Concurrent claim for OrderRef " . $orderRef);
|
||||
echo "<h1>تمت معالجة هذا الطلب مسبقاً</h1><p>الدفعة قيد المعالجة، يرجى التحقق من رصيدك في التطبيق.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate token for driver wallet.');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate token for siro wallet.');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID.');
|
||||
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([':driverID' => $driverId, ':paymentID' => $paymentID, ':amount' => $bonusAmount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Failed to insert into driverWallet.');
|
||||
|
||||
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenDriver->execute([':token' => $tokenDriver]);
|
||||
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt) VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([':driverId' => $driverId, ':passengerId' => 'driver', ':amount' => $originalAmount, ':paymentMethod' => $paymentMethod, ':token' => $tokenSiro]);
|
||||
|
||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||
|
||||
$con->commit();
|
||||
|
||||
echo "<h1>تمت العملية بنجاح</h1><p>تمت إضافة الرصيد إلى محفظتك. يمكنك الآن العودة إلى التطبيق.</p>";
|
||||
|
||||
} catch (Throwable $e) {
|
||||
if ($con->inTransaction()) { $con->rollBack(); }
|
||||
error_log("VERIFY_ERROR: " . $e->getMessage() . " | OrderRef: " . $orderRef);
|
||||
echo "<h1>حدث خطأ</h1><p>لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef) . "</p>";
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// استخدام ملف اتصال خاص بالـ Webhook لا يحتوي على أي تحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف Webhook النهائي الخاص بـ eCash (مع تسجيل إضافي للتصحيح)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// --- الإعدادات ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID');
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET');
|
||||
|
||||
// --- إعداد ملف اللوج (Log File) ---
|
||||
$log_dir = __DIR__ . '/../logs';
|
||||
$log_file = $log_dir . '/ecash_production.log';
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
// --- قراءة البيانات القادمة من eCash ---
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// --- تسجيل الـ Callback كاملاً لأغراض المراقبة ---
|
||||
file_put_contents($log_file, "--- NEW WEBHOOK ---\n" . date('Y-m-d H:i:s') . " - RAW BODY: " . $raw_body . PHP_EOL, FILE_APPEND);
|
||||
|
||||
if (!$data || !isset($data['Token'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- استخراج البيانات ---
|
||||
$isSuccess = $data['IsSuccess'] ?? false;
|
||||
$transactionNo = $data['TransactionNo'] ?? '';
|
||||
$amount = $data['Amount'] ?? '';
|
||||
$orderRef = $data['OrderRef'] ?? '';
|
||||
$receivedToken = $data['Token'];
|
||||
|
||||
// --- **تصحيح الأخطاء: بناء وتسجيل سلسلة التحقق** ---
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $transactionNo . $amount . $orderRef;
|
||||
$expectedToken = strtoupper(md5($verification_string));
|
||||
|
||||
// تسجيل السلسلة المستخدمة في التوقيع والقيم الفردية
|
||||
$debug_log = "VERIFICATION STRING: " . $verification_string . PHP_EOL;
|
||||
$debug_log .= " - Merchant ID Used: " . $ecash_merchant_id . PHP_EOL;
|
||||
$debug_log .= " - TransactionNo Used: " . $transactionNo . PHP_EOL;
|
||||
$debug_log .= " - Amount Used: " . $amount . PHP_EOL;
|
||||
$debug_log .= " - OrderRef Used: " . $orderRef . PHP_EOL;
|
||||
$debug_log .= "CALCULATED TOKEN: " . $expectedToken . PHP_EOL;
|
||||
$debug_log .= "RECEIVED TOKEN: " . $receivedToken . PHP_EOL;
|
||||
|
||||
file_put_contents($log_file, $debug_log, FILE_APPEND);
|
||||
|
||||
|
||||
// --- التحقق من صحة الـ Token ---
|
||||
if (!hash_equals($expectedToken, $receivedToken)) {
|
||||
http_response_code(401);
|
||||
file_put_contents($log_file, "TOKEN MISMATCH! Process stopped." . PHP_EOL, FILE_APPEND);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- تحديث حالة الدفعة في قاعدة البيانات ---
|
||||
file_put_contents($log_file, "TOKEN MATCH! Proceeding to update database." . PHP_EOL, FILE_APPEND);
|
||||
$payment_status = $isSuccess ? 1 : 0;
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyriaDriver` SET status = :status, updated_at = NOW() WHERE order_ref = :order_ref AND status = 2"
|
||||
);
|
||||
$stmt->execute([
|
||||
':status' => $payment_status,
|
||||
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "SUCCESS: Database updated." . PHP_EOL, FILE_APPEND);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "INFO: Order not found or already processed." . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
file_put_contents($log_file, "FATAL: Database update failed: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// هذا الملف يجب أن يستخدم ملف الاتصال الذي يتحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
// يجب استدعاء دالة التحقق هنا لضمان أن الطلب قادم من تطبيقك فقط
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف إتمام الدفع النهائي
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب من تطبيق فلاتر بعد عودة المستخدم.
|
||||
| 2. التحقق من وجود دفعة ناجحة حديثة للمستخدم في قاعدة البيانات.
|
||||
| 3. حساب المكافآت.
|
||||
| 4. استدعاء واجهات API داخلية لإضافة الرصيد إلى المحافظ.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$userId = filterRequest("userId"); // أو driverId
|
||||
$paymentMethod = filterRequest("paymentMethod") ?? 'ecash';
|
||||
|
||||
if (empty($userId)) {
|
||||
printFailure("معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// خطوة 1: البحث عن آخر دفعة ناجحة للمستخدم (تم تحديثها بواسطة الـ Webhook)
|
||||
$stmt = $con->prepare(
|
||||
"SELECT * FROM `paymentsLogSyria`
|
||||
WHERE user_id = :user_id
|
||||
AND status = 1
|
||||
AND updated_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->bindParam(':user_id', $userId, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
printFailure("لم يتم العثور على دفعة ناجحة حديثة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// خطوة 2: الحصول على المبلغ (لا يحتاج للقسمة على 100)
|
||||
$amount = $payment['amount'];
|
||||
|
||||
// خطوة 3: حساب المكافأة
|
||||
$finalAmount = calculateBonus($amount); // استخدم دالة حساب المكافآت الخاصة بك
|
||||
|
||||
$passengerId = $userId; // نفترض أن معرّف المستخدم هو نفسه معرّف الراكب
|
||||
|
||||
// --- هنا تضع نفس منطق إضافة الرصيد الذي كان في ملف payment_verify.php القديم ---
|
||||
// (مثال)
|
||||
// $token = generatePaymentToken($passengerId, $finalAmount);
|
||||
// addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
// ... إلخ
|
||||
|
||||
// --- النجاح النهائي ---
|
||||
printSuccess("تمت معالجة الدفع وتحديث الرصيد بنجاح.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Finalize Payment Error: " . $e->getMessage());
|
||||
printFailure("حدث خطأ في قاعدة البيانات أثناء إتمام العملية.");
|
||||
}
|
||||
|
||||
// --- يمكنك وضع دوال المساعدة هنا (calculateBonus, etc.) ---
|
||||
function calculateBonus($amount) {
|
||||
$result = $amount;
|
||||
if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 100) $result = 100.0;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php"; // تأكد من أن هذا الملف يحتوي على الاتصال بقاعدة البيانات ودوال المساعدة
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف بدء الدفع مع eCash
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب الدفع من تطبيق فلاتر (المبلغ ومعرّف المستخدم/السائق).
|
||||
| 2. إنشاء رابط دفع فريد وخاص ببوابة eCash.
|
||||
| 3. حساب رمز التحقق (Verification Code) المطلوب من eCash.
|
||||
| 4. تسجيل محاولة الدفع في قاعدة البيانات بحالة "قيد الانتظار".
|
||||
| 5. إعادة رابط الدفع إلى التطبيق ليتم عرضه في WebView.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- الإعدادات الرئيسية - يجب تخزينها كمتغيرات بيئة (Environment Variables) ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID'); // معرّف التاجر الخاص بك من eCash
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET'); // المفتاح السري الخاص بك من eCash
|
||||
$ecash_terminal_key = getenv('ECASH_TERMINAL_KEY'); // مفتاح المحطة الطرفية (Terminal Key) من eCash
|
||||
$ecash_checkout_url = 'https://checkout.ecash-pay.com/'; //
|
||||
$ecash_checkout_url_stage = 'https://checkout.ecash-pay.co/';//رابط بوابة الدفع
|
||||
$base_app_url = getenv('APP_BASE_URL'); // الرابط الأساسي لواجهة API الخاصة بك
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$amount = filterRequest("amount");
|
||||
$driverId = filterRequest("driverId"); // معرّف السائق أو المستخدم
|
||||
|
||||
// --- التحقق من صحة البيانات المدخلة ---
|
||||
if (empty($amount) || empty($driverId) || !is_numeric($amount) || $amount <= 0) {
|
||||
printFailure("المبلغ أو معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعداد متغيرات الدفع ---
|
||||
$currency = "SYP"; // العملة حسب متطلبات eCash
|
||||
$lang = "AR"; // لغة واجهة الدفع (AR أو EN)
|
||||
//$orderRef = uniqid($driverId . "_"); // إنشاء رقم مرجعي فريد للطلب لربطه بالمستخدم
|
||||
$orderRef = "tripz_" . $driverId . "_" . time();
|
||||
// --- إنشاء رمز التحقق (Verification Code) ---
|
||||
// هو عبارة عن MD5 لمجموعة من الحقول ويجب أن يكون بأحرف كبيرة
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($verification_string));
|
||||
|
||||
// --- تحديد روابط إعادة التوجيه والاستدعاء (Redirect & Callback) ---
|
||||
// الرابط الذي يتم توجيه المستخدم إليه بعد إتمام الدفع
|
||||
$redirectUrl = urlencode($base_app_url . "/driver/ecash_verify.php?orderRef=" . $orderRef);
|
||||
// الرابط الذي تستدعيه eCash لإبلاغ سيرفرك بنتيجة العملية (Webhook)
|
||||
$callbackUrl = urlencode($base_app_url . "/driver/ecash_webhook.php");
|
||||
|
||||
// --- بناء رابط الدفع النهائي الخاص بـ eCash ---
|
||||
$paymentUrl = "{$ecash_checkout_url}Checkout/CardCheckout" .
|
||||
"?tk=" . urlencode($ecash_terminal_key) .
|
||||
"&mid=" . urlencode($ecash_merchant_id) .
|
||||
"&vc=" . urlencode($verificationCode) .
|
||||
"&c=" . urlencode($currency) .
|
||||
"&a=" . urlencode($amount) .
|
||||
"&lang=" . urlencode($lang) .
|
||||
"&or=" . urlencode($orderRef) .
|
||||
"&ru=" . $redirectUrl .
|
||||
"&cu=" . $callbackUrl;
|
||||
|
||||
// --- تسجيل العملية المبدئية في قاعدة البيانات ---
|
||||
// هذا يساعد على تتبع الطلب وربطه بالـ callback القادم من eCash
|
||||
// نفترض أن حقل status يقبل القيم: 0=فشل، 1=نجاح، 2=قيد الانتظار
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO `paymentsLogSyriaDriver`( `user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (:user_id, :amount, 2, :order_ref,'ecash-driver', NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
':user_id' => $driverId,
|
||||
':amount' => $amount,
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $e->getMessage());
|
||||
printFailure("حدث خطأ أثناء بدء عملية الدفع. يرجى المحاولة مرة أخرى.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعادة رابط الدفع إلى تطبيق فلاتر ---
|
||||
// التطبيق سيستقبل هذا الرابط ويفتحه في WebView
|
||||
// نرسل الرابط داخل حقل 'message' كما يتوقع كود فلاتر
|
||||
printSuccess($paymentUrl);
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
// **FIX:** Corrected the path to go up three levels to find the 'vendor' directory
|
||||
require_once realpath(__DIR__ . '/../../../vendor/autoload.php');
|
||||
// **FIX:** Corrected the path to go up two levels to find 'load_env.php'
|
||||
require_once realpath(__DIR__ . '/../../load_env.php');
|
||||
|
||||
$env_file = '/home/tripz-egypt-wl/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
// --- Database Connection ONLY ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Webhook DB Connection Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal Server Error']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// --- ecash_config.php ---
|
||||
// Central configuration file for ecash, loading from a .env file.
|
||||
|
||||
// This assumes you have a function or a library (like Dotenv) to load the .env file.
|
||||
|
||||
|
||||
// --- IMPORTANT ---
|
||||
// Define the path to your .env file. Adjust if necessary.
|
||||
//$env_file_path = '/home/tripz-egypt-wl/env/.env'; // Or use realpath(__DIR__ . '/../.env');
|
||||
//loadEnvironment($env_file_path);
|
||||
require "../../jwtconnect.php";
|
||||
// --- Load ecash Credentials from Environment Variables ---
|
||||
define('ECASH_MERCHANT_ID', getenv('ECASH_MERCHANT_ID'));
|
||||
define('ECASH_MERCHANT_SECRET', getenv('ECASH_MERCHANT_SECRET'));
|
||||
define('ECASH_TERMINAL_KEY', getenv('ECASH_TERMINAL_KEY'));
|
||||
|
||||
// --- Set Mode (Staging/Live) from Environment Variable ---
|
||||
// Add ECASH_STAGING_MODE=true to your .env for testing
|
||||
$is_staging = getenv('ECASH_STAGING_MODE') === 'false';
|
||||
define('ECASH_STAGING_MODE', $is_staging);
|
||||
|
||||
// --- URLs (Automatically switch based on mode) ---
|
||||
$checkout_base_url = ECASH_STAGING_MODE ? 'https://checkout.ecash-pay.co' : 'https://checkout.ecash-pay.com';
|
||||
define('ECASH_CHECKOUT_URL', $checkout_base_url);
|
||||
|
||||
// --- Your Application URLs (Load from .env or define here) ---
|
||||
// It's best practice to also put these in your .env file.
|
||||
define('APP_BASE_URL', getenv('APP_BASE_URL')); // e.g., https://yourdomain.com/api
|
||||
define('APP_REDIRECT_URL_SUCCESS', APP_BASE_URL . '/success.php');
|
||||
define('APP_CALLBACK_URL', APP_BASE_URL . '/webhook_ecash.php'); // Use a specific webhook for ecash
|
||||
|
||||
// --- Other Settings ---
|
||||
define('ECASH_CURRENCY', 'SYP');
|
||||
define('ECASH_LANG', 'EN'); // 'EN' for English, 'AR' for Arabic
|
||||
|
||||
// --- Basic Validation ---
|
||||
if (!ECASH_MERCHANT_ID || !ECASH_MERCHANT_SECRET || !ECASH_TERMINAL_KEY) {
|
||||
http_response_code(500);
|
||||
error_log("ecash config: Missing one or more required ecash environment variables.");
|
||||
echo json_encode(['status' => 'error', 'message' => 'Payment gateway not configured correctly.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
// هذا الملف هو نقطة النهاية بعد الدفع، ويقوم بكل عمليات التحقق وإضافة الرصيد
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function showHTMLPage($type, $title, $message) {
|
||||
$color = $type === 'success' ? '#28a745' : '#dc3545';
|
||||
$icon = $type === 'success' ? '✔' : '✖';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($title) ?></title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f4f6f9;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-align: center;
|
||||
padding-top: 100px;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
background: #fff;
|
||||
padding: 40px 30px;
|
||||
margin: auto;
|
||||
max-width: 450px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.1);
|
||||
animation: fadeIn 1s ease-out;
|
||||
}
|
||||
.icon {
|
||||
font-size: 64px;
|
||||
color: <?= $color ?>;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: <?= $color ?>;
|
||||
}
|
||||
p {
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
color: #555;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon"><?= $icon ?></div>
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
<p><?= htmlspecialchars($message) ?></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
showHTMLPage("error", "خطأ في الرابط", "الرقم المرجعي للطلب غير موجود.");
|
||||
}
|
||||
|
||||
$payment = null;
|
||||
$max_attempts = 5;
|
||||
for ($attempts = 0; $attempts < $max_attempts; $attempts++) {
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyria` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($payment) break;
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
if (!$payment) {
|
||||
logError("VERIFY", "لم يتم تأكيد الدفع بعد عدة محاولات", ["orderRef" => $orderRef]);
|
||||
showHTMLPage("error", "لم يتم تأكيد الدفع", "لم نتمكن من تأكيد دفعتك بعد. قد تستغرق العملية بضع لحظات. يرجى التحقق من رصيدك في التطبيق لاحقاً أو التواصل مع الدعم الفني.");
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
if (!$token) throw new Exception("فشل إنشاء توكن محفظة الراكب");
|
||||
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
if (!$walletResult || ($walletResult['status'] ?? 'fail') != "success") {
|
||||
throw new Exception("فشل إضافة الرصيد لمحفظة الراكب");
|
||||
}
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
if (!$siroToken) throw new Exception("فشل إنشاء توكن محفظة سفر");
|
||||
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
if (!$siroWalletResult || ($siroWalletResult['status'] ?? 'fail') != "success") {
|
||||
throw new Exception("فشل إضافة الرصيد لمحفظة سفر");
|
||||
}
|
||||
|
||||
logError("VERIFY", "اكتملت العملية بنجاح", ["orderRef" => $orderRef, "userId" => $userId]);
|
||||
showHTMLPage("success", "تم الدفع بنجاح", "تمت إضافة الرصيد إلى محفظتك. شكرًا لاستخدامك Intaleq.");
|
||||
} catch (Exception $e) {
|
||||
logError("VERIFY_ERROR", $e->getMessage(), ["orderRef" => $orderRef]);
|
||||
showHTMLPage("error", "حدث خطأ", "لقد تم استلام دفعتك بنجاح، ولكن حدث خطأ أثناء تحديث رصيدك. يرجى التواصل مع الدعم الفني وتزويدهم بالرقم المرجعي: " . htmlspecialchars($orderRef));
|
||||
}
|
||||
|
||||
// --- دوال مساعدة ---
|
||||
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 200000) return 205000;
|
||||
if ($amount == 400000) return 425000;
|
||||
if ($amount == 1000000) return 1040000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// استخدام ملف اتصال خاص بالـ Webhook لا يحتوي على أي تحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف Webhook النهائي الخاص بـ eCash (مع تسجيل إضافي للتصحيح)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// --- الإعدادات ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID');
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET');
|
||||
|
||||
// --- إعداد ملف اللوج (Log File) ---
|
||||
$log_dir = __DIR__ . '/../logs';
|
||||
$log_file = $log_dir . '/ecash_production.log';
|
||||
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
// --- قراءة البيانات القادمة من eCash ---
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// --- تسجيل الـ Callback كاملاً لأغراض المراقبة ---
|
||||
file_put_contents($log_file, "--- NEW WEBHOOK ---\n" . date('Y-m-d H:i:s') . " - RAW BODY: " . $raw_body . PHP_EOL, FILE_APPEND);
|
||||
|
||||
if (!$data || !isset($data['Token'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- استخراج البيانات ---
|
||||
$isSuccess = $data['IsSuccess'] ?? false;
|
||||
$transactionNo = $data['TransactionNo'] ?? '';
|
||||
$amount = $data['Amount'] ?? '';
|
||||
$orderRef = $data['OrderRef'] ?? '';
|
||||
$receivedToken = $data['Token'];
|
||||
|
||||
// --- **تصحيح الأخطاء: بناء وتسجيل سلسلة التحقق** ---
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $transactionNo . $amount . $orderRef;
|
||||
$expectedToken = strtoupper(md5($verification_string));
|
||||
|
||||
// تسجيل السلسلة المستخدمة في التوقيع والقيم الفردية
|
||||
$debug_log = "VERIFICATION STRING: " . $verification_string . PHP_EOL;
|
||||
$debug_log .= " - Merchant ID Used: " . $ecash_merchant_id . PHP_EOL;
|
||||
$debug_log .= " - TransactionNo Used: " . $transactionNo . PHP_EOL;
|
||||
$debug_log .= " - Amount Used: " . $amount . PHP_EOL;
|
||||
$debug_log .= " - OrderRef Used: " . $orderRef . PHP_EOL;
|
||||
$debug_log .= "CALCULATED TOKEN: " . $expectedToken . PHP_EOL;
|
||||
$debug_log .= "RECEIVED TOKEN: " . $receivedToken . PHP_EOL;
|
||||
|
||||
file_put_contents($log_file, $debug_log, FILE_APPEND);
|
||||
|
||||
|
||||
// --- التحقق من صحة الـ Token ---
|
||||
if (!hash_equals($expectedToken, $receivedToken)) {
|
||||
http_response_code(401);
|
||||
file_put_contents($log_file, "TOKEN MISMATCH! Process stopped." . PHP_EOL, FILE_APPEND);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- تحديث حالة الدفعة في قاعدة البيانات ---
|
||||
file_put_contents($log_file, "TOKEN MATCH! Proceeding to update database." . PHP_EOL, FILE_APPEND);
|
||||
$payment_status = $isSuccess ? 1 : 0;
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = :status, updated_at = NOW() WHERE order_ref = :order_ref AND status = 2"
|
||||
);
|
||||
$stmt->execute([
|
||||
':status' => $payment_status,
|
||||
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "SUCCESS: Database updated." . PHP_EOL, FILE_APPEND);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
file_put_contents($log_file, "INFO: Order not found or already processed." . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
file_put_contents($log_file, "FATAL: Database update failed: " . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
// هذا الملف يجب أن يستخدم ملف الاتصال الذي يتحقق من الهوية
|
||||
include "../../../jwtconnect.php";
|
||||
// يجب استدعاء دالة التحقق هنا لضمان أن الطلب قادم من تطبيقك فقط
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف إتمام الدفع النهائي
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب من تطبيق فلاتر بعد عودة المستخدم.
|
||||
| 2. التحقق من وجود دفعة ناجحة حديثة للمستخدم في قاعدة البيانات.
|
||||
| 3. حساب المكافآت.
|
||||
| 4. استدعاء واجهات API داخلية لإضافة الرصيد إلى المحافظ.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$userId = filterRequest("userId"); // أو driverId
|
||||
$paymentMethod = filterRequest("paymentMethod") ?? 'ecash';
|
||||
|
||||
if (empty($userId)) {
|
||||
printFailure("معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// خطوة 1: البحث عن آخر دفعة ناجحة للمستخدم (تم تحديثها بواسطة الـ Webhook)
|
||||
$stmt = $con->prepare(
|
||||
"SELECT * FROM `paymentsLogSyria`
|
||||
WHERE user_id = :user_id
|
||||
AND status = 1
|
||||
AND updated_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
$stmt->bindParam(':user_id', $userId, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
printFailure("لم يتم العثور على دفعة ناجحة حديثة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// خطوة 2: الحصول على المبلغ (لا يحتاج للقسمة على 100)
|
||||
$amount = $payment['amount'];
|
||||
|
||||
// خطوة 3: حساب المكافأة
|
||||
$finalAmount = calculateBonus($amount); // استخدم دالة حساب المكافآت الخاصة بك
|
||||
|
||||
$passengerId = $userId; // نفترض أن معرّف المستخدم هو نفسه معرّف الراكب
|
||||
|
||||
// --- هنا تضع نفس منطق إضافة الرصيد الذي كان في ملف payment_verify.php القديم ---
|
||||
// (مثال)
|
||||
// $token = generatePaymentToken($passengerId, $finalAmount);
|
||||
// addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
// ... إلخ
|
||||
|
||||
// --- النجاح النهائي ---
|
||||
printSuccess("تمت معالجة الدفع وتحديث الرصيد بنجاح.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Finalize Payment Error: " . $e->getMessage());
|
||||
printFailure("حدث خطأ في قاعدة البيانات أثناء إتمام العملية.");
|
||||
}
|
||||
|
||||
// --- يمكنك وضع دوال المساعدة هنا (calculateBonus, etc.) ---
|
||||
function calculateBonus($amount) {
|
||||
$result = $amount;
|
||||
if ($amount == 500) return 530;
|
||||
if ($amount == 1000) return 1070;
|
||||
if ($amount == 2000) return 2180;
|
||||
if ($amount == 5000) return 5700;
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php"; // تأكد من أن هذا الملف يحتوي على الاتصال بقاعدة البيانات ودوال المساعدة
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| ملف بدء الدفع مع eCash
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| هذا الملف مسؤول عن:
|
||||
| 1. استقبال طلب الدفع من تطبيق فلاتر (المبلغ ومعرّف المستخدم/السائق).
|
||||
| 2. إنشاء رابط دفع فريد وخاص ببوابة eCash.
|
||||
| 3. حساب رمز التحقق (Verification Code) المطلوب من eCash.
|
||||
| 4. تسجيل محاولة الدفع في قاعدة البيانات بحالة "قيد الانتظار".
|
||||
| 5. إعادة رابط الدفع إلى التطبيق ليتم عرضه في WebView.
|
||||
|
|
||||
*/
|
||||
|
||||
// --- الإعدادات الرئيسية - يجب تخزينها كمتغيرات بيئة (Environment Variables) ---
|
||||
$ecash_merchant_id = getenv('ECASH_MERCHANT_ID'); // معرّف التاجر الخاص بك من eCash
|
||||
$ecash_merchant_secret = getenv('ECASH_MERCHANT_SECRET'); // المفتاح السري الخاص بك من eCash
|
||||
$ecash_terminal_key = getenv('ECASH_TERMINAL_KEY'); // مفتاح المحطة الطرفية (Terminal Key) من eCash
|
||||
$ecash_checkout_url = 'https://checkout.ecash-pay.com/'; //
|
||||
$ecash_checkout_url_stage = 'https://checkout.ecash-pay.co/';//رابط بوابة الدفع
|
||||
$base_app_url = getenv('APP_BASE_URL'); // الرابط الأساسي لواجهة API الخاصة بك
|
||||
|
||||
// --- استقبال البيانات من تطبيق فلاتر ---
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId"); // معرّف السائق أو المستخدم
|
||||
|
||||
// --- التحقق من صحة البيانات المدخلة ---
|
||||
if (empty($amount) || empty($passengerId) || !is_numeric($amount) || $amount <= 0) {
|
||||
printFailure("المبلغ أو معرّف المستخدم غير صالح.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعداد متغيرات الدفع ---
|
||||
$currency = "SYP"; // العملة حسب متطلبات eCash
|
||||
$lang = "AR"; // لغة واجهة الدفع (AR أو EN)
|
||||
//$orderRef = uniqid($passengerId . "_"); // إنشاء رقم مرجعي فريد للطلب لربطه بالمستخدم
|
||||
$orderRef = "tripz_" . $passengerId . "_" . time();
|
||||
// --- إنشاء رمز التحقق (Verification Code) ---
|
||||
// هو عبارة عن MD5 لمجموعة من الحقول ويجب أن يكون بأحرف كبيرة
|
||||
$verification_string = $ecash_merchant_id . $ecash_merchant_secret . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($verification_string));
|
||||
|
||||
// --- تحديد روابط إعادة التوجيه والاستدعاء (Redirect & Callback) ---
|
||||
// الرابط الذي يتم توجيه المستخدم إليه بعد إتمام الدفع
|
||||
$redirectUrl = urlencode($base_app_url . "/passenger/ecash_verify.php?orderRef=" . $orderRef);
|
||||
// الرابط الذي تستدعيه eCash لإبلاغ سيرفرك بنتيجة العملية (Webhook)
|
||||
$callbackUrl = urlencode($base_app_url . "/passenger/ecash_webhook.php");
|
||||
|
||||
// --- بناء رابط الدفع النهائي الخاص بـ eCash ---
|
||||
$paymentUrl = "{$ecash_checkout_url}Checkout/CardCheckout" .
|
||||
"?tk=" . urlencode($ecash_terminal_key) .
|
||||
"&mid=" . urlencode($ecash_merchant_id) .
|
||||
"&vc=" . urlencode($verificationCode) .
|
||||
"&c=" . urlencode($currency) .
|
||||
"&a=" . urlencode($amount) .
|
||||
"&lang=" . urlencode($lang) .
|
||||
"&or=" . urlencode($orderRef) .
|
||||
"&ru=" . $redirectUrl .
|
||||
"&cu=" . $callbackUrl;
|
||||
//error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $paymentUrl);
|
||||
// --- تسجيل العملية المبدئية في قاعدة البيانات ---
|
||||
// هذا يساعد على تتبع الطلب وربطه بالـ callback القادم من eCash
|
||||
// نفترض أن حقل status يقبل القيم: 0=فشل، 1=نجاح، 2=قيد الانتظار
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO `paymentsLogSyria`( `user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (:user_id, :amount, 2, :order_ref,'ecash-passenger', NOW())"
|
||||
);
|
||||
$stmt->execute([
|
||||
':user_id' => $passengerId,
|
||||
':amount' => $amount,
|
||||
':order_ref' => $orderRef
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
error_log("eCash - فشل تسجيل الدفعة المبدئية: " . $e->getMessage());
|
||||
printFailure("حدث خطأ أثناء بدء عملية الدفع. يرجى المحاولة مرة أخرى.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- إعادة رابط الدفع إلى تطبيق فلاتر ---
|
||||
// التطبيق سيستقبل هذا الرابط ويفتحه في WebView
|
||||
// نرسل الرابط داخل حقل 'message' كما يتوقع كود فلاتر
|
||||
printSuccess($paymentUrl);
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
// Load environment variables from .env file
|
||||
// **FIX:** Corrected the path to go up three levels to find the 'vendor' directory
|
||||
require_once realpath(__DIR__ . '/../../../vendor/autoload.php');
|
||||
// **FIX:** Corrected the path to go up two levels to find 'load_env.php'
|
||||
require_once realpath(__DIR__ . '/../../load_env.php');
|
||||
|
||||
$env_file = '/home/tripz-egypt-wl/env/.env';
|
||||
loadEnvironment($env_file);
|
||||
|
||||
// --- CORS Headers ---
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type");
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$dbname = getenv('dbname');
|
||||
// --- Database Connection ONLY ---
|
||||
try {
|
||||
$dsn = "mysql:host=localhost;dbname=$dbname;charset=utf8mb4";
|
||||
$options = [
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"
|
||||
];
|
||||
$user = getenv('USER');
|
||||
$pass = getenv('PASS');
|
||||
$con = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Webhook DB Connection Error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Internal Server Error']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// --- payWithEcash.php (Updated) ---
|
||||
// This script now saves transaction details before generating the payment link.
|
||||
|
||||
require "../../jwtconnect.php"; // Your existing connection/auth script
|
||||
require_once "ecash_config.php"; // The ecash config file
|
||||
|
||||
// --- Get Input Data ---
|
||||
$amount = filterRequest("amount", "numeric");
|
||||
$passengerId = filterRequest("passengerId"); // Get passengerId from the request
|
||||
|
||||
if (!$amount || $amount <= 0) {
|
||||
printFailure("Invalid or missing amount.");
|
||||
exit;
|
||||
}
|
||||
if (!$passengerId) {
|
||||
printFailure("Passenger ID is required.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// The user ID from your JWT authentication in jwtconnect.php
|
||||
$userId = $decodedToken->user_id ?? null;
|
||||
if (!$userId) {
|
||||
printFailure("Authentication failed.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. --- Create a unique order reference ---
|
||||
$orderRef = 'INTALEQ_' . $userId . '_' . time();
|
||||
|
||||
// 2. --- Save the initial transaction to your database ---
|
||||
// This step is CRITICAL for the webhook to work correctly.
|
||||
// Create a table named 'ecash_transactions' with columns like:
|
||||
// id, order_ref, user_id, passenger_id, amount, status, created_at, updated_at
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"INSERT INTO ecash_transactions (order_ref, user_id, passenger_id, amount, status) VALUES (?, ?, ?, ?, 'pending')"
|
||||
);
|
||||
$stmt->execute([$orderRef, $userId, $passengerId, $amount]);
|
||||
} catch (PDOException $e) {
|
||||
// Log the database error
|
||||
error_log("ecash_initiate DB Error: " . $e->getMessage());
|
||||
printFailure("Failed to initiate payment transaction.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. --- Generate the Verification Code (VC) ---
|
||||
$stringToHash = ECASH_MERCHANT_ID . ECASH_MERCHANT_SECRET . $amount . $orderRef;
|
||||
$verificationCode = strtoupper(md5($stringToHash));
|
||||
|
||||
// 4. --- Construct URLs ---
|
||||
$redirectUrl = urlencode(APP_REDIRECT_URL_SUCCESS);
|
||||
$callbackUrl = urlencode(APP_CALLBACK_URL);
|
||||
|
||||
// 5. --- Build the Final Checkout URL ---
|
||||
$checkoutUrl = sprintf(
|
||||
"%s/Checkout/CardCheckout?tk=%s&mid=%s&vc=%s&c=%s&a=%s&lang=%s&or=%s&ru=%s&cu=%s",
|
||||
ECASH_CHECKOUT_URL,
|
||||
ECASH_TERMINAL_KEY,
|
||||
ECASH_MERCHANT_ID,
|
||||
$verificationCode,
|
||||
ECASH_CURRENCY,
|
||||
$amount,
|
||||
ECASH_LANG,
|
||||
$orderRef,
|
||||
$redirectUrl,
|
||||
$callbackUrl
|
||||
);
|
||||
|
||||
// 6. --- Return the URL to Flutter ---
|
||||
printSuccess($checkoutUrl);
|
||||
|
||||
?>
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
|
||||
<?php
|
||||
// --- webhook_ecash.php ---
|
||||
// This script securely handles the callback from ecash and updates user wallets.
|
||||
|
||||
// Include necessary files
|
||||
require_once "../../jwtconnect.php"; // Adjust path as needed
|
||||
require_once "ecash_config.php"; // Adjust path as needed
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/ecash_webhook.log");
|
||||
|
||||
// --- Start Webhook Processing ---
|
||||
|
||||
// 1. Log the raw incoming data from ecash
|
||||
$raw_post_data = file_get_contents('php://input');
|
||||
logError("0", "Webhook received", ["payload" => $raw_post_data]);
|
||||
|
||||
$data = json_decode($raw_post_data, true);
|
||||
if (!$data) {
|
||||
logError("0.1", "Invalid JSON payload.");
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Extract data and verify the token from ecash
|
||||
$isSuccess = $data['isSuccess'] ?? null;
|
||||
$orderRef = $data['orderRef'] ?? null;
|
||||
$transactionNo = $data['transactionNo'] ?? null;
|
||||
$amount = $data['amount'] ?? null;
|
||||
$receivedToken = $data['token'] ?? '';
|
||||
|
||||
$string_to_hash = ECASH_MERCHANT_ID . ECASH_MERCHANT_SECRET . $transactionNo . $amount . $orderRef;
|
||||
$expected_token = md5($string_to_hash);
|
||||
|
||||
if (strcasecmp($expected_token, $receivedToken) !== 0) {
|
||||
logError("1", "Token Mismatch", [
|
||||
"expected" => $expected_token,
|
||||
"received" => $receivedToken,
|
||||
"string" => $string_to_hash
|
||||
]);
|
||||
http_response_code(401); // Unauthorized
|
||||
exit;
|
||||
}
|
||||
logError("1", "Token Verified Successfully.");
|
||||
|
||||
// 3. Check if payment was successful
|
||||
if ($isSuccess !== true) {
|
||||
logError("2", "Payment was not successful according to ecash.", $data);
|
||||
// Optionally, update your database to mark the order as 'failed'
|
||||
updateTransactionStatus($orderRef, 'failed', $transactionNo);
|
||||
http_response_code(200); // Respond OK to ecash, but do nothing else
|
||||
exit;
|
||||
}
|
||||
logError("2", "Payment reported as SUCCESS by ecash.");
|
||||
|
||||
// 4. Find and process the transaction atomically
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM ecash_transactions WHERE order_ref = ? LIMIT 1 FOR UPDATE");
|
||||
$stmt->execute([$orderRef]);
|
||||
$transaction = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$transaction) {
|
||||
$con->rollBack();
|
||||
logError("3", "OrderRef not found in our database.", ["orderRef" => $orderRef]);
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($transaction['status'] !== 'pending') {
|
||||
$con->rollBack();
|
||||
logError("3.1", "Transaction already processed.", ["orderRef" => $orderRef, "status" => $transaction['status']]);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Atomically mark as processing to prevent concurrent webhooks
|
||||
$lockStmt = $con->prepare("UPDATE ecash_transactions SET status = 'processing' WHERE order_ref = ? AND status = 'pending'");
|
||||
$lockStmt->execute([$orderRef]);
|
||||
if ($lockStmt->rowCount() === 0) {
|
||||
$con->rollBack();
|
||||
logError("3.2", "Concurrent webhook detected, transaction already claimed.", ["orderRef" => $orderRef]);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerId = $transaction['passenger_id'];
|
||||
$paidAmount = $transaction['amount'];
|
||||
logError("3", "Transaction found in DB.", ["passengerId" => $passengerId, "amount" => $paidAmount]);
|
||||
|
||||
$finalAmount = calculateBonus($paidAmount);
|
||||
logError("4", "Bonus calculated.", ["original" => $paidAmount, "final" => $finalAmount]);
|
||||
|
||||
$passengerToken = generatePaymentToken($passengerId, $finalAmount);
|
||||
if ($passengerToken) {
|
||||
addToPassengerWallet($passengerId, $finalAmount, $passengerToken);
|
||||
}
|
||||
|
||||
$paymentMethod = 'ecash';
|
||||
addToSiroWallet($passengerId, $paidAmount, $paymentMethod);
|
||||
|
||||
$stmtUpdate = $con->prepare("UPDATE ecash_transactions SET status = 'success', ecash_transaction_no = ?, updated_at = NOW() WHERE order_ref = ?");
|
||||
$stmtUpdate->execute([$transactionNo, $orderRef]);
|
||||
|
||||
$con->commit();
|
||||
logError("7", "Process completed successfully.");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
logError("DB_ERROR", "Database error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
logError("GENERAL_ERROR", "General error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 7. Respond to ecash server
|
||||
http_response_code(200);
|
||||
echo "Webhook processed.";
|
||||
|
||||
|
||||
// --- ALL HELPER FUNCTIONS FROM paymet_verfy.php ---
|
||||
|
||||
function updateTransactionStatus($orderRef, $status, $transactionNo) {
|
||||
global $con;
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE ecash_transactions SET status = ?, ecash_transaction_no = ?, updated_at = NOW() WHERE order_ref = ?"
|
||||
);
|
||||
$stmt->execute([$status, $transactionNo, $orderRef]);
|
||||
} catch (PDOException $e) {
|
||||
logError("DB_UPDATE_ERROR", "Failed to update transaction status", ["error" => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
|
||||
$postData = [
|
||||
'passengerId' => $passengerId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("4.1", "cURL error in token generation", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("4.2", "HTTP error in token generation", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !isset($data['message'])) {
|
||||
logError("4.3", "Invalid response format in token generation", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data['message']; // ✅ Return token
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to passenger's wallet with error logging
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'passenger_id' => $passengerId,
|
||||
'balance' => $amount,
|
||||
'token' => $token
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("5.1", "cURL error in passenger wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("5.2", "HTTP error in passenger wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("5.3", "Invalid response format in passenger wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to Siro wallet with error logging
|
||||
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod) {
|
||||
|
||||
|
||||
// Generate a new token specifically for the Siro wallet
|
||||
$siroToken = generatePaymentToken($passengerId, $amount);
|
||||
|
||||
if (!$siroToken) {
|
||||
logError("6.0.1", "Failed to generate Siro token");
|
||||
return null;
|
||||
}
|
||||
|
||||
logError("6.0.2", "Generated new Siro token", [
|
||||
"token_length" => ($siroToken)
|
||||
]);
|
||||
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $siroToken, // Use the new Siro-specific token
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("6.1", "cURL error in Siro wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("6.2", "HTTP error in Siro wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("6.3", "Invalid response format in Siro wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
|
||||
// 🎯 Function to calculate bonus
|
||||
function calculateBonus($amount) {
|
||||
logError("3.1", "Bonus calculation input", ["amount" => $amount]);
|
||||
|
||||
$result = 0;
|
||||
if ($amount == 100) $result = 100;
|
||||
else if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
|
||||
logError("3.2", "Bonus calculation result", [
|
||||
"input" => $amount,
|
||||
"output" => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/driver/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// It's better to use __DIR__ for reliable file path resolution.
|
||||
// Assuming your private_key.pem is in the same directory as this script.
|
||||
$privateKeyPath = __DIR__ . "/private_key.pem";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents($privateKeyPath));
|
||||
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // The OTP
|
||||
|
||||
error_log("MTN Confirm (Driver): Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
error_log("MTN Confirm (Driver): Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Encrypt the code
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
$body = [
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => intval($operationNumber),
|
||||
'Code' => $codeB64
|
||||
];
|
||||
$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
error_log("MTN Confirm (Driver): Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// Generate signature
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult) {
|
||||
error_log("MTN Confirm (Driver): Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
error_log("MTN Confirm (Driver): Generated signature");
|
||||
|
||||
// Send the request
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("MTN Confirm (Driver): HTTP $httpCode - Response: $response");
|
||||
if ($curlError) {
|
||||
error_log("MTN Confirm (Driver): cURL error - $curlError");
|
||||
}
|
||||
|
||||
// --- SOLUTION IMPLEMENTED HERE ---
|
||||
// 1. Decode the response from MTN to check its contents.
|
||||
$responseData = json_decode($response, true) ?: [];
|
||||
|
||||
// 2. First, check for network/gateway level failure.
|
||||
if ($httpCode !== 200) {
|
||||
error_log("MTN Confirm (Driver): HTTP failure for invoice {$invoice}. Code: {$httpCode}");
|
||||
// Use printFailure to send a structured error to Flutter
|
||||
printFailure(['message' => 'MTN Gateway Error', 'http' => $httpCode, 'details' => $responseData]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Now, check for business-logic failure (like "Balance not enough").
|
||||
$errno = $responseData['Errno'] ?? -1; // Default to an error state if Errno is missing
|
||||
if ($errno !== 0) {
|
||||
$apiError = $responseData['Error'] ?? 'Unknown MTN API Error';
|
||||
error_log("MTN Confirm (Driver): Business failure for invoice {$invoice}. Errno: {$errno}, Reason: {$apiError}");
|
||||
// This now sends the specific error message in the format Flutter expects!
|
||||
printFailure(['message' => $apiError, 'errno' => $errno, 'details' => $responseData]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- ONLY PROCEED TO DATABASE ON FULL SUCCESS (HTTP 200 AND Errno 0) ---
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyriaDriver` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
error_log("MTN Confirm (Driver): Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
// The file path correction from before remains important.
|
||||
include_once __DIR__ . '/finalize_wallet_payment.php';
|
||||
|
||||
// Call the wallet finalization logic
|
||||
if (function_exists('finalizeWalletPayment')) {
|
||||
$_GET['orderRef'] = $invoice;
|
||||
finalizeWalletPayment($con);
|
||||
} else {
|
||||
error_log("MTN Confirm (Driver): FATAL - finalizeWalletPayment() function does not exist after include.");
|
||||
}
|
||||
|
||||
// On success, send a success response to Flutter
|
||||
printSuccess(['message' => 'Payment confirmed successfully', 'details' => $responseData]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("MTN Confirm (Driver): DB update error - " . $e->getMessage());
|
||||
printFailure("Database processing error.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// wallet/finalize_wallet_payment.php
|
||||
include_once "../../../jwtconnect.php";
|
||||
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $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 finalizeWalletPayment($con) {
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
logError("FINALIZE", "Missing orderRef");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. تحقق من الدفع
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("FINALIZE", "Payment not found or not completed", ['orderRef' => $orderRef]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
// حساب المكافأة
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// إنشاء التوكنات
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate driver token');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate siro token');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID');
|
||||
|
||||
// driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonusAmount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed');
|
||||
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenDriver]);
|
||||
|
||||
// siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenSiro]);
|
||||
|
||||
logError("FINALIZE", "Wallets updated successfully", ['orderRef' => $orderRef]);
|
||||
printSuccess("FINALIZE", "Wallets updated successfully");
|
||||
} catch (Throwable $e) {
|
||||
logError("FINALIZE", "Exception during finalization: " . $e->getMessage(), ['orderRef' => $orderRef]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// File: generate_keys.php
|
||||
// الوظيفة: إنشاء زوج المفاتيح (العام والخاص) لمرة واحدة فقط
|
||||
|
||||
// إعدادات لتوليد المفتاح
|
||||
$config = [
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 1024,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
// إنشاء زوج المفاتيح
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if (!$res) {
|
||||
die('Failed to generate new private key. Error: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// استخراج المفتاح الخاص
|
||||
openssl_pkey_export($res, $private_key);
|
||||
|
||||
// استخراج المفتاح العام
|
||||
$public_key_details = openssl_pkey_get_details($res);
|
||||
$public_key = $public_key_details["key"];
|
||||
|
||||
// حفظ المفاتيح في ملفات
|
||||
file_put_contents('private_key.pem', $private_key);
|
||||
file_put_contents('public_key.pem', $public_key);
|
||||
|
||||
echo "<h1>Keys Generated Successfully!</h1>";
|
||||
echo "<h2>Private Key (saved to private_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($private_key) . "</pre>";
|
||||
echo "<h2>Public Key (saved to public_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($public_key) . "</pre>";
|
||||
|
||||
// --- تحضير المفتاح العام لعملية التفعيل ---
|
||||
// إزالة الهيدر والفوتر والأسطر الجديدة كما هو مطلوب
|
||||
$formatted_public_key = str_replace("-----BEGIN PUBLIC KEY-----", "", $public_key);
|
||||
$formatted_public_key = str_replace("-----END PUBLIC KEY-----", "", $formatted_public_key);
|
||||
$formatted_public_key = preg_replace("/\s+/", "", $formatted_public_key);
|
||||
|
||||
|
||||
echo "<h2>Formatted Public Key (for Terminal Activation):</h2>";
|
||||
echo "<p><strong>انسخ هذا المفتاح لاستخدامه في خطوة تفعيل الجهاز (activate_terminal.php)</strong></p>";
|
||||
echo "<textarea rows='5' cols='80' readonly>" . htmlspecialchars($formatted_public_key) . "</textarea>";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/initiate_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
|
||||
$invoice = filterRequest('invoice'); // رقم الفاتورة
|
||||
$phone = filterRequest('phone'); // رقم الزبون
|
||||
$guid = uniqid('mtn_');
|
||||
|
||||
if (!$invoice || !$phone) {
|
||||
printFailure("Missing invoice or phone.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = json_encode([
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid
|
||||
], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$hash = hash('sha256', $body, true);
|
||||
$pkey = openssl_get_privatekey($privateKeyPem);
|
||||
if (!$pkey) {
|
||||
error_log("[MTN Initiate] Failed to load private key");
|
||||
printFailure("Payment configuration error");
|
||||
exit;
|
||||
}
|
||||
openssl_sign($hash, $sig, $pkey, OPENSSL_ALGO_SHA256);
|
||||
openssl_free_key($pkey);
|
||||
$xSignature = base64_encode($sig);
|
||||
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/initiate");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/initiate",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// سجل المحاولة مع Guid
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `mtn_payments`
|
||||
SET guid = :guid, status = 3, updated_at = NOW()
|
||||
WHERE invoice = :inv"
|
||||
);
|
||||
$stmt->execute([':guid'=>$guid, ':inv'=>$invoice]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// بيانات التفعيل
|
||||
$terminalId = "9001000000060863";
|
||||
$activationCode = "26164711";
|
||||
$serialNumber = "INTALEQ-001"; // يمكنك تغييره
|
||||
|
||||
// المفتاح العام على سطر واحد — بدون BEGIN/END وبدون أسطر جديدة
|
||||
//$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNxFbepx2OrpyrNG4+/aAaH3Rjc8dGw6B6vMAfsZzzm4wzoSkrtsr6jfuaMTZRLwxS5h8k1ztLG1HrOmL/NDsiE/7yxaKLAIZyWB/rR9byvPeOCC8QnCd/08kmxNl/l7Akn6qlPwsVpKUUNsr0SkU9lShMAw4OBgQq399jsbkFSwIDAQAB";
|
||||
$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOhVAdUyxFpVNSyjRndMWEPAN9vJEetMzLbjF9DTn2lPVuRj/Mkwq9wCNhy+tdeX2lIn4K3EkONBvYJubBhxnYOoQuMchPW5vG7VnmpLjZ7TkpM2n9fcMu8u1GkLatLblDI4LTfvn3851+nhpnYlUVkjw5GAhH4XnEpveIjqDhzQIDAQAB";
|
||||
// جسم الطلب
|
||||
$body = [
|
||||
"Key" => $publicKey,
|
||||
"Secret" => $activationCode,
|
||||
"Serial" => $serialNumber
|
||||
];
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES);
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)),'"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// 1. توليد هاش SHA256 للـ JSON
|
||||
//$bodyHash = hash('sha256', $bodyJson, true);
|
||||
|
||||
// 2. تحميل المفتاح الخاص للتوقيع
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem")); // تأكد من وجود هذا الملف بجانب السكربت
|
||||
|
||||
// 3. توقيع الهاش
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
|
||||
// 4. تحويل التوقيع إلى Base64
|
||||
$xSignature = base64_encode($signature);
|
||||
|
||||
// 5. إرسال الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: pos_web/pos/activate",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init("https://cashmobile.mtnsyr.com:9000");
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// ✅ النتيجة
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
"httpCode" => $httpCode,
|
||||
"response" => json_decode($response, true),
|
||||
"sentBody" => $body,
|
||||
]);
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
date_default_timezone_set("Asia/Damascus");
|
||||
|
||||
// ========== إعدادات MTN ==========
|
||||
$terminalId = "9001000000060863";
|
||||
$currencyCode = 760;
|
||||
$sessionNumber = 0;
|
||||
$ttl = 15;
|
||||
|
||||
// ====== استقبال البيانات من فلاتر ======
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$phone = filterRequest("phone");
|
||||
|
||||
// ✅ Log مبدئي
|
||||
error_log("🚦 START | passengerId: $passengerId | phone: $phone | amount: $amount");
|
||||
|
||||
// تحقق من المدخلات
|
||||
if (empty($amount) || empty($passengerId) || empty($phone) || $amount <= 0) {
|
||||
error_log("❌ Invalid input: amount=$amount, passengerId=$passengerId, phone=$phone");
|
||||
printFailure("بيانات الدفع غير كاملة أو غير صالحة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== توليد invoiceNumber و GUID ======
|
||||
$invoiceNumber = mt_rand(10000000000, 99999999999);
|
||||
//$invoiceNumber = "MTN_" . $passengerId . "_" . time();
|
||||
$guid = uniqid("mtn_");
|
||||
error_log("🧾 Generated Invoice: $invoiceNumber");
|
||||
error_log("🧭 Generated GUID: $guid");
|
||||
|
||||
// ====== 1. إنشاء الفاتورة ======
|
||||
$createInvoiceBody = [
|
||||
"Amount" => intval($amount * 100),
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Session" => $sessionNumber,
|
||||
"TTL" => $ttl
|
||||
];
|
||||
error_log("📦 Create Invoice Body: " . json_encode($createInvoiceBody, JSON_UNESCAPED_UNICODE));
|
||||
$invoiceResponse = sendMtnApiRequest("pos_web/invoice/create", $terminalId, $createInvoiceBody);
|
||||
error_log("📥 Create Invoice Response: " . json_encode($invoiceResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$invoiceResponse || isset($invoiceResponse['Errno']) && $invoiceResponse['Errno'] != 0) {
|
||||
error_log("❌ Failed to create invoice. Error: " . json_encode($invoiceResponse));
|
||||
printFailure("فشل إنشاء الفاتورة عبر MTN.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 2. بدء الدفع ======
|
||||
$initiateBody = [
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Phone" => $phone,
|
||||
"Guid" => $guid
|
||||
];
|
||||
error_log("📤 body initiateBody: $initiateBody");
|
||||
error_log("📦 Initiate Payment Body: " . json_encode($initiateBody, JSON_UNESCAPED_UNICODE));
|
||||
$initiateResponse = sendMtnApiRequest("pos_web/payment_phone/initiate", $terminalId, $initiateBody);
|
||||
error_log("📥 Initiate Response: " . json_encode($initiateResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$initiateResponse || !isset($initiateResponse['OperationNumber'])) {
|
||||
error_log("❌ Failed to initiate payment.");
|
||||
printFailure($initiateResponse);
|
||||
exit;
|
||||
}
|
||||
|
||||
$operationNumber = $initiateResponse['OperationNumber'];
|
||||
|
||||
// ====== 3. تسجيل العملية ======
|
||||
try {
|
||||
$stmt = $con->prepare("INSERT INTO `paymentsLogSyriaDriver`
|
||||
(`user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (?, ?, 2, ?, 'mtn', NOW())");
|
||||
$stmt->execute([$passengerId, $amount, $invoiceNumber]);
|
||||
error_log("✅ DB Log Inserted.");
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ DB ERROR: " . $e->getMessage());
|
||||
printFailure("فشل في تسجيل العملية.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 4. نجاح
|
||||
error_log("✅ Payment initiation successful.");
|
||||
printSuccess([
|
||||
"invoiceNumber" => $invoiceNumber,
|
||||
"operationNumber" => $operationNumber,
|
||||
"guid" => $guid
|
||||
]);
|
||||
|
||||
|
||||
// ====== دالة إرسال الطلب =====================
|
||||
function sendMtnApiRequest($requestName, $terminalId, $body)
|
||||
{
|
||||
$apiUrl = "https://cashmobile.mtnsyr.com:9000";
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
|
||||
// ✅ تحويل الـ body إلى JSON بدون فراغات أو أسطر
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// ✅ توليد التوقيع
|
||||
// $bodyHash = hash('sha256', $bodyJson, true);
|
||||
error_log("📤 body before JSON: $bodyJson");
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
$xSignature = base64_encode($signature);
|
||||
error_log("📤 body xSignature: $xSignature");
|
||||
// ✅ رؤوس الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: $requestName",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// ✅ لوق داخلي
|
||||
error_log("🔐 Signature for $requestName: $xSignature");
|
||||
error_log("📤 Sent JSON: $bodyJson");
|
||||
|
||||
curl_close($ch);
|
||||
return json_decode($response, true);
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
// --- 2. استقبال البيانات من الطلب ---
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$driver_name = filterRequest("driver_name");
|
||||
$amount = filterRequest("amount");
|
||||
$wallet_type = filterRequest("wallet_type");
|
||||
$wallet_number = filterRequest("wallet_number");
|
||||
|
||||
// التحقق من أن البيانات الأساسية موجودة
|
||||
if (empty($driver_id) || empty($driver_name) || empty($amount) || empty($wallet_type) || empty($wallet_number)) {
|
||||
printFailure('Missing required fields.');
|
||||
exit();
|
||||
}
|
||||
|
||||
// --- 3. إدراج الطلب في قاعدة البيانات ---
|
||||
try {
|
||||
$sql = "INSERT INTO driver_withdrawal_requests (driver_id, driver_name, amount, wallet_type, wallet_number) VALUES (?, ?, ?, ?, ?)";
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// تنفيذ الاستعلام مع تمرير البيانات
|
||||
$success = $stmt->execute([
|
||||
$driver_id,
|
||||
$driver_name,
|
||||
$amount,
|
||||
$wallet_type,
|
||||
$wallet_number
|
||||
]);
|
||||
|
||||
if ($success) {
|
||||
// --- 4. الحصول على رقم الطلب وإرسال إشعار واتساب ---
|
||||
$transaction_id = $con->lastInsertId(); // الحصول على رقم التعريف الخاص بالطلب الجديد
|
||||
sendWhatsAppNotification($transaction_id, $driver_name, $amount, $wallet_type, $wallet_number);
|
||||
|
||||
// إرسال استجابة نجاح إلى التطبيق
|
||||
printSuccess("Withdrawal request saved and notification sent.");
|
||||
|
||||
} else {
|
||||
printFailure('Failed to save withdrawal request.');
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// التعامل مع أخطاء قاعدة البيانات
|
||||
error_log("Database Error in request_withdrawal.php: " . $e->getMessage());
|
||||
printFailure('A database error occurred.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* دالة لإرسال إشعار إلى خدمة العملاء عبر RaseelPlus API
|
||||
*/
|
||||
function sendWhatsAppNotification($transaction_id, $driver_name, $amount, $wallet_type, $wallet_number) {
|
||||
|
||||
// استخدام متغيرات البيئة (Environment Variables) هو الطريقة الأكثر أماناً لإدارة المعلومات الحساسة
|
||||
// بدلاً من كتابتها مباشرة في الكود.
|
||||
$customer_service_number = getenv('CUSTOMER_SERVICE_WHATSAPP');
|
||||
// $customer_service_number = "9639XXXXXXXX"; // كرقم احتياطي مؤقت
|
||||
|
||||
// نص الرسالة مع إضافة رقم الطلب
|
||||
$messageBody = "طلب سحب جديد (رقم الطلب: #$transaction_id):\n\n" .
|
||||
"اسم السائق: " . $driver_name . "\n" .
|
||||
"المبلغ: " . $amount . " ل.س\n" .
|
||||
"نوع المحفظة: " . $wallet_type . "\n" .
|
||||
"رقم المحفظة: " . $wallet_number;
|
||||
|
||||
// بيانات الطلب (Payload) للـ API
|
||||
$payload = [
|
||||
"number" => $customer_service_number,
|
||||
"type" => "text",
|
||||
"message" => $messageBody,
|
||||
"instance_id" => getenv('instance_idWhatsApp');
|
||||
"access_token" => getenv('access_tokenWhatsApp');
|
||||
];
|
||||
|
||||
// استدعاء الـ API
|
||||
// ملاحظة: لا نتحقق من استجابة الـ API هنا لأن العملية الرئيسية (حفظ الطلب) قد نجحت بالفعل.
|
||||
// يمكن إضافة تسجيل للأخطاء إذا لزم الأمر.
|
||||
callAPI_Withdrawal("POST", "https://raseelplus.com/api/send", json_encode($payload));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* دالة لإجراء استدعاءات API باستخدام cURL
|
||||
*/
|
||||
function callAPI_Withdrawal($method, $url, $data) {
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Accept: application/json"
|
||||
],
|
||||
]);
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
// تسجيل الخطأ في سجلات الخادم للمراجعة لاحقًا
|
||||
error_log("[callAPI_Withdrawal] cURL Error #: " . $err);
|
||||
return null;
|
||||
} else {
|
||||
return json_decode($response);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // الـ OTP
|
||||
|
||||
error_log("MTN Confirm: Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
error_log("MTN Confirm: Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تشفير الكود
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
$body = [
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => intval($operationNumber),
|
||||
'Code' => $codeB64
|
||||
];
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
|
||||
error_log("MTN Confirm: Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// توليد التوقيع
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult) {
|
||||
error_log("MTN Confirm: Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
error_log("MTN Confirm: Generated signature");
|
||||
|
||||
// إرسال الطلب
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
error_log("MTN Confirm: HTTP $httpCode - Response: $response");
|
||||
if ($curlError) {
|
||||
error_log("MTN Confirm: cURL error - $curlError");
|
||||
}
|
||||
|
||||
// تحديث قاعدة البيانات في حال نجاح
|
||||
if ($httpCode === 200) {
|
||||
try {
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
error_log("MTN Confirm: Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLogSyria WHERE order_ref = :order_ref LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $invoice]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($payment) {
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'mtn';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
|
||||
printSuccess('MTN Confirm');
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("MTN Confirm: DB update error - " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
error_log("MTN Confirm: Payment failed with HTTP code $httpCode");
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 200000) return 205000;
|
||||
if ($amount == 400000) return 425000;
|
||||
if ($amount == 1000000) return 1040000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
// wallet/finalize_wallet_payment.php
|
||||
include_once "../../../jwtconnect.php";
|
||||
|
||||
define("LOG_FILE", "../logs/payment_verification.log");
|
||||
|
||||
function logError($step, $message, $data = null) {
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) { mkdir($logDir, 0755, true); }
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] STEP {$step}: {$message}";
|
||||
if ($data !== null) { $logEntry .= " | Data: " . json_encode($data, JSON_UNESCAPED_UNICODE); }
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
$data = $driverId . $amount . time() . ($secretKey ?? 'default_secret');
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([':token' => $token, ':driverID' => $driverId, ':amount' => $amount]);
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $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 finalizeWalletPayment($con) {
|
||||
$orderRef = $_GET['orderRef'] ?? null;
|
||||
if (empty($orderRef)) {
|
||||
logError("FINALIZE", "Missing orderRef");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. تحقق من الدفع
|
||||
$stmt = $con->prepare("SELECT * FROM `paymentsLogSyriaDriver` WHERE order_ref = :order_ref AND status = 1 LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $orderRef]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("FINALIZE", "Payment not found or not completed", ['orderRef' => $orderRef]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$driverId = $payment['user_id'];
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$paymentMethod = $payment['payment_method'] ?? 'ecash';
|
||||
|
||||
// حساب المكافأة
|
||||
$bonusAmount = match ((int)$originalAmount) {
|
||||
10000 => 10000.0,
|
||||
20000 => 21000.0,
|
||||
40000 => 45000.0,
|
||||
100000 => 110000.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// إنشاء التوكنات
|
||||
$tokenDriver = generateToken($con, $driverId, $bonusAmount);
|
||||
if (!$tokenDriver) throw new Exception('Failed to generate driver token');
|
||||
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) throw new Exception('Failed to generate siro token');
|
||||
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod);
|
||||
if (!$paymentID) throw new Exception('Failed to generate payment ID');
|
||||
|
||||
// driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonusAmount,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
if ($insertDriver->rowCount() === 0) throw new Exception('Insert to driverWallet failed');
|
||||
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenDriver]);
|
||||
|
||||
// siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token")->execute([':token' => $tokenSiro]);
|
||||
|
||||
logError("FINALIZE", "Wallets updated successfully", ['orderRef' => $orderRef]);
|
||||
printSuccess("FINALIZE", "Wallets updated successfully");
|
||||
} catch (Throwable $e) {
|
||||
logError("FINALIZE", "Exception during finalization: " . $e->getMessage(), ['orderRef' => $orderRef]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// File: generate_keys.php
|
||||
// الوظيفة: إنشاء زوج المفاتيح (العام والخاص) لمرة واحدة فقط
|
||||
|
||||
// إعدادات لتوليد المفتاح
|
||||
$config = [
|
||||
"digest_alg" => "sha256",
|
||||
"private_key_bits" => 1024,
|
||||
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
// إنشاء زوج المفاتيح
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if (!$res) {
|
||||
die('Failed to generate new private key. Error: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// استخراج المفتاح الخاص
|
||||
openssl_pkey_export($res, $private_key);
|
||||
|
||||
// استخراج المفتاح العام
|
||||
$public_key_details = openssl_pkey_get_details($res);
|
||||
$public_key = $public_key_details["key"];
|
||||
|
||||
// حفظ المفاتيح في ملفات
|
||||
file_put_contents('private_key.pem', $private_key);
|
||||
file_put_contents('public_key.pem', $public_key);
|
||||
|
||||
echo "<h1>Keys Generated Successfully!</h1>";
|
||||
echo "<h2>Private Key (saved to private_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($private_key) . "</pre>";
|
||||
echo "<h2>Public Key (saved to public_key.pem):</h2>";
|
||||
echo "<pre>" . htmlspecialchars($public_key) . "</pre>";
|
||||
|
||||
// --- تحضير المفتاح العام لعملية التفعيل ---
|
||||
// إزالة الهيدر والفوتر والأسطر الجديدة كما هو مطلوب
|
||||
$formatted_public_key = str_replace("-----BEGIN PUBLIC KEY-----", "", $public_key);
|
||||
$formatted_public_key = str_replace("-----END PUBLIC KEY-----", "", $formatted_public_key);
|
||||
$formatted_public_key = preg_replace("/\s+/", "", $formatted_public_key);
|
||||
|
||||
|
||||
echo "<h2>Formatted Public Key (for Terminal Activation):</h2>";
|
||||
echo "<p><strong>انسخ هذا المفتاح لاستخدامه في خطوة تفعيل الجهاز (activate_terminal.php)</strong></p>";
|
||||
echo "<textarea rows='5' cols='80' readonly>" . htmlspecialchars($formatted_public_key) . "</textarea>";
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/initiate_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
|
||||
$invoice = filterRequest('invoice'); // رقم الفاتورة
|
||||
$phone = filterRequest('phone'); // رقم الزبون
|
||||
$guid = uniqid('mtn_');
|
||||
|
||||
if (!$invoice || !$phone) {
|
||||
printFailure("Missing invoice or phone.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = json_encode([
|
||||
'Invoice' => intval($invoice),
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid
|
||||
], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$hash = hash('sha256', $body, true);
|
||||
$pkey = openssl_get_privatekey($privateKeyPem);
|
||||
if (!$pkey) {
|
||||
error_log("[MTN Initiate] Failed to load private key");
|
||||
printFailure("Payment configuration error");
|
||||
exit;
|
||||
}
|
||||
openssl_sign($hash, $sig, $pkey, OPENSSL_ALGO_SHA256);
|
||||
openssl_free_key($pkey);
|
||||
$xSignature = base64_encode($sig);
|
||||
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/initiate");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/initiate",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
]
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// سجل المحاولة مع Guid
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `mtn_payments`
|
||||
SET guid = :guid, status = 3, updated_at = NOW()
|
||||
WHERE invoice = :inv"
|
||||
);
|
||||
$stmt->execute([':guid'=>$guid, ':inv'=>$invoice]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpCode);
|
||||
echo $response;
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
// بيانات التفعيل
|
||||
$terminalId = "9001000000060863";
|
||||
$activationCode = "26164711";
|
||||
$serialNumber = "INTALEQ-001"; // يمكنك تغييره
|
||||
|
||||
// المفتاح العام على سطر واحد — بدون BEGIN/END وبدون أسطر جديدة
|
||||
//$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNxFbepx2OrpyrNG4+/aAaH3Rjc8dGw6B6vMAfsZzzm4wzoSkrtsr6jfuaMTZRLwxS5h8k1ztLG1HrOmL/NDsiE/7yxaKLAIZyWB/rR9byvPeOCC8QnCd/08kmxNl/l7Akn6qlPwsVpKUUNsr0SkU9lShMAw4OBgQq399jsbkFSwIDAQAB";
|
||||
$publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOhVAdUyxFpVNSyjRndMWEPAN9vJEetMzLbjF9DTn2lPVuRj/Mkwq9wCNhy+tdeX2lIn4K3EkONBvYJubBhxnYOoQuMchPW5vG7VnmpLjZ7TkpM2n9fcMu8u1GkLatLblDI4LTfvn3851+nhpnYlUVkjw5GAhH4XnEpveIjqDhzQIDAQAB";
|
||||
// جسم الطلب
|
||||
$body = [
|
||||
"Key" => $publicKey,
|
||||
"Secret" => $activationCode,
|
||||
"Serial" => $serialNumber
|
||||
];
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES);
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)),'"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// 1. توليد هاش SHA256 للـ JSON
|
||||
//$bodyHash = hash('sha256', $bodyJson, true);
|
||||
|
||||
// 2. تحميل المفتاح الخاص للتوقيع
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem")); // تأكد من وجود هذا الملف بجانب السكربت
|
||||
|
||||
// 3. توقيع الهاش
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
|
||||
// 4. تحويل التوقيع إلى Base64
|
||||
$xSignature = base64_encode($signature);
|
||||
|
||||
// 5. إرسال الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: en",
|
||||
"Request-Name: pos_web/pos/activate",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init("https://cashmobile.mtnsyr.com:9000");
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// ✅ النتيجة
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
"httpCode" => $httpCode,
|
||||
"response" => json_decode($response, true),
|
||||
"sentBody" => $body,
|
||||
]);
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
// /v1/main/ride/mtn/passenger/confirm_payment.php
|
||||
include "../../../jwtconnect.php";
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
function mlog(string $msg) { error_log($msg); } // timestamp يضاف تلقائياً من PHP
|
||||
|
||||
// قاعدة URL للتطبيق (لمعالجة BASE_URL غير المعروفة)
|
||||
if (!defined('BASE_URL')) {
|
||||
$APP_BASE_URL = rtrim(getenv('APP_BASE_URL') ?: '', '/');
|
||||
if ($APP_BASE_URL === '') {
|
||||
$scheme = isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'https';
|
||||
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
|
||||
define('BASE_URL', $scheme . '://' . $host);
|
||||
} else {
|
||||
define('BASE_URL', $APP_BASE_URL);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$baseUrl = rtrim(getenv('MTN_API_BASE_URL'), '/');
|
||||
$terminalId = getenv('MTN_TERMINAL_ID');
|
||||
$privateKeyPem = getenv('MTN_PRIVATE_KEY');
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
$invoice = filterRequest('invoiceNumber');
|
||||
$phone = filterRequest('phone');
|
||||
$guid = filterRequest('guid');
|
||||
$operationNumber = filterRequest('operationNumber');
|
||||
$code = filterRequest('otp'); // الـ OTP
|
||||
$lang = filterRequest("lang");
|
||||
|
||||
mlog("MTN Confirm: Start request for invoice={$invoice}, phone={$phone}, guid={$guid}, opNum={$operationNumber}");
|
||||
|
||||
if (!$invoice || !$phone || !$guid || !$operationNumber || !$code) {
|
||||
mlog("MTN Confirm: Missing parameters");
|
||||
printFailure("Missing parameters.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// تشفير الكود (SHA256 ثم Base64)
|
||||
$hashBin = hash('sha256', $code, true);
|
||||
$codeB64 = base64_encode($hashBin);
|
||||
|
||||
// جسم الطلب نحو MTN
|
||||
$body = [
|
||||
'Invoice' => (int)$invoice,
|
||||
'Phone' => $phone,
|
||||
'Guid' => $guid,
|
||||
'OperationNumber' => (int)$operationNumber,
|
||||
'Code' => $codeB64,
|
||||
'Accept-Language' => $lang
|
||||
];
|
||||
$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE);
|
||||
mlog("MTN Confirm: Prepared body JSON: " . $bodyJson);
|
||||
|
||||
// توقيع الجسم
|
||||
$sig = null;
|
||||
$signResult = openssl_sign($bodyJson, $sig, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
if (!$signResult || !$sig) {
|
||||
mlog("MTN Confirm: Failed to generate signature");
|
||||
printFailure("Signature error.");
|
||||
exit;
|
||||
}
|
||||
$xSignature = base64_encode($sig);
|
||||
mlog("MTN Confirm: Generated signature");
|
||||
|
||||
// إرسال الطلب إلى MTN
|
||||
$ch = curl_init("{$baseUrl}/pos_web/payment_phone/confirm");
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $bodyJson,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Content-Type: application/json",
|
||||
"Request-Name: pos_web/payment_phone/confirm",
|
||||
"Subject: {$terminalId}",
|
||||
"X-Signature: {$xSignature}"
|
||||
],
|
||||
CURLOPT_TIMEOUT => 25,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
mlog("MTN Confirm: HTTP {$httpCode} - Response: " . ($response ?? ''));
|
||||
if ($curlError) {
|
||||
mlog("MTN Confirm: cURL error - {$curlError}");
|
||||
}
|
||||
|
||||
// فك JSON لرد MTN (حتى لو خطأ) لعرض سبب واضح
|
||||
$mtn = json_decode($response ?: '{}', true);
|
||||
if (!is_array($mtn)) {
|
||||
$mtn = [];
|
||||
}
|
||||
|
||||
// 🧠 سياسة القرار:
|
||||
// - إذا HTTP≠200 → فشل شبكة/بوابة
|
||||
// - إذا HTTP=200 لكن Errno≠0 → خطأ من MTN (مثل Incorrect sms code)
|
||||
// - فقط إذا HTTP=200 && Errno=0 → نجاح، نحدّث DB ونضيف للمحافظ
|
||||
if ($httpCode !== 200) {
|
||||
// لا تحدّث DB
|
||||
printFailure([
|
||||
'message' => 'MTN confirm HTTP failure',
|
||||
'http' => $httpCode,
|
||||
'mtn' => $mtn
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// HTTP 200 — افحص Errno
|
||||
$errno = isset($mtn['Errno']) ? (int)$mtn['Errno'] : null;
|
||||
if ($errno !== 0) {
|
||||
// لا تحدّث DB في هذه الحالة
|
||||
$errText = isset($mtn['Error']) ? $mtn['Error'] : 'Unknown MTN error';
|
||||
// اطبع لوج مشابه للمثال المطلوب
|
||||
// مثال: {"Errno":662,"Error":"Incorrect sms code","Abuse":2,"Transaction":""}
|
||||
mlog("MTN Confirm: Business failure from MTN - Errno={$errno}, Error=" . json_encode($mtn, JSON_UNESCAPED_UNICODE));
|
||||
printFailure([
|
||||
'message' => $errText,
|
||||
'errno' => $errno,
|
||||
'mtn' => $mtn
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ✅ نجاح كامل من MTN — تحديث DB ثم المحافظ
|
||||
try {
|
||||
global $con;
|
||||
|
||||
$stmt = $con->prepare(
|
||||
"UPDATE `paymentsLogSyria` SET status = 1, updated_at = NOW()
|
||||
WHERE order_ref = :inv"
|
||||
);
|
||||
$stmt->execute([':inv' => $invoice]);
|
||||
mlog("MTN Confirm: Payment updated successfully in DB for invoice={$invoice}");
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLogSyria WHERE order_ref = :order_ref LIMIT 1");
|
||||
$stmt->execute([':order_ref' => $invoice]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
mlog("MTN Confirm: Payment row not found after update");
|
||||
printFailure("Payment row not found");
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $payment['user_id'];
|
||||
$amount = $payment['amount'];
|
||||
$paymentMethod = $payment['payment_method'] ?? 'mtn';
|
||||
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
$token = generatePaymentToken($userId, $finalAmount);
|
||||
$walletResult = addToPassengerWallet($userId, $finalAmount, $token);
|
||||
|
||||
$siroToken = generatePaymentToken($userId, $amount);
|
||||
$siroWalletResult = addToSiroWallet($userId, $amount, $paymentMethod, $siroToken);
|
||||
|
||||
// رجّع رد موحّد + ضمّن رد MTN
|
||||
printSuccess([
|
||||
'message' => 'MTN Confirm',
|
||||
'data' => [
|
||||
'invoice' => $invoice,
|
||||
'finalAmount' => $finalAmount,
|
||||
'wallet' => $walletResult,
|
||||
'siroWallet' => $siroWalletResult,
|
||||
'mtn' => $mtn
|
||||
]
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
mlog("MTN Confirm: DB update error - " . $e->getMessage());
|
||||
printFailure("DB error");
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
mlog("MTN Confirm: Exception - " . $e->getMessage());
|
||||
printFailure("Server error");
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* نفس دوال المساعدة، لكن باستعمال BASE_URL المؤمّنة أعلاه
|
||||
*/
|
||||
function calculateBonus($amount) {
|
||||
if ($amount == 20000) return 20500;
|
||||
if ($amount == 40000) return 42500;
|
||||
if ($amount == 100000) return 104000;
|
||||
return $amount;
|
||||
}
|
||||
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = rtrim(BASE_URL, '/') . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
$postData = ['passengerId' => $passengerId, 'amount' => $amount];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
$data = json_decode($response, true);
|
||||
return $data['message'] ?? null;
|
||||
}
|
||||
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = rtrim(BASE_URL, '/') . "/passengerWallet/add.php";
|
||||
$postData = ['passenger_id' => $passengerId, 'balance' => $amount, 'token' => $token];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod, $token) {
|
||||
$url = rtrim(BASE_URL, '/') . "/siroWallet/add.php";
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $token,
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200 || !$response) return null;
|
||||
return json_decode($response, true);
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
date_default_timezone_set("Asia/Damascus");
|
||||
|
||||
// ========== إعدادات MTN ==========
|
||||
$terminalId = "9001000000060863";
|
||||
$currencyCode = 760;
|
||||
$sessionNumber = 0;
|
||||
$ttl = 15;
|
||||
|
||||
// ====== استقبال البيانات من فلاتر ======
|
||||
$amount = filterRequest("amount");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$phone = filterRequest("phone");
|
||||
$lang = filterRequest("lang");
|
||||
|
||||
// ✅ Log مبدئي
|
||||
error_log("🚦 START | passengerId: $passengerId | phone: $phone | amount: $amount");
|
||||
|
||||
// تحقق من المدخلات
|
||||
if (empty($amount) || empty($passengerId) || empty($phone) || $amount <= 0) {
|
||||
error_log("❌ Invalid input: amount=$amount, passengerId=$passengerId, phone=$phone");
|
||||
printFailure("بيانات الدفع غير كاملة أو غير صالحة.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== توليد invoiceNumber و GUID ======
|
||||
$invoiceNumber = mt_rand(10000000000, 99999999999);
|
||||
//$invoiceNumber = "MTN_" . $passengerId . "_" . time();
|
||||
$guid = uniqid("mtn_");
|
||||
error_log("🧾 Generated Invoice: $invoiceNumber");
|
||||
error_log("🧭 Generated GUID: $guid");
|
||||
|
||||
// ====== 1. إنشاء الفاتورة ======
|
||||
$createInvoiceBody = [
|
||||
"Amount" => intval($amount * 100),
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Session" => $sessionNumber,
|
||||
"TTL" => $ttl
|
||||
];
|
||||
error_log("📦 Create Invoice Body: " . json_encode($createInvoiceBody, JSON_UNESCAPED_UNICODE));
|
||||
$invoiceResponse = sendMtnApiRequest("pos_web/invoice/create", $terminalId, $createInvoiceBody);
|
||||
error_log("📥 Create Invoice Response: " . json_encode($invoiceResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$invoiceResponse || isset($invoiceResponse['Errno']) && $invoiceResponse['Errno'] != 0) {
|
||||
error_log("❌ Failed to create invoice. Error: " . json_encode($invoiceResponse));
|
||||
printFailure("فشل إنشاء الفاتورة عبر MTN.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 2. بدء الدفع ======
|
||||
$initiateBody = [
|
||||
"Invoice" => $invoiceNumber,
|
||||
"Phone" => $phone,
|
||||
"Guid" => $guid
|
||||
];
|
||||
error_log("📤 body initiateBody: $initiateBody");
|
||||
error_log("📦 Initiate Payment Body: " . json_encode($initiateBody, JSON_UNESCAPED_UNICODE));
|
||||
$initiateResponse = sendMtnApiRequest("pos_web/payment_phone/initiate", $terminalId, $initiateBody);
|
||||
error_log("📥 Initiate Response: " . json_encode($initiateResponse, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
if (!$initiateResponse || !isset($initiateResponse['OperationNumber'])) {
|
||||
error_log("❌ Failed to initiate payment.");
|
||||
printFailure($initiateResponse);
|
||||
exit;
|
||||
}
|
||||
|
||||
$operationNumber = $initiateResponse['OperationNumber'];
|
||||
|
||||
// ====== 3. تسجيل العملية ======
|
||||
try {
|
||||
$stmt = $con->prepare("INSERT INTO `paymentsLogSyria`
|
||||
(`user_id`, `amount`, `status`, `order_ref`, `payment_method`, `created_at`)
|
||||
VALUES (?, ?, 2, ?, 'mtn', NOW())");
|
||||
$stmt->execute([$passengerId, $amount, $invoiceNumber]);
|
||||
error_log("✅ DB Log Inserted.");
|
||||
} catch (PDOException $e) {
|
||||
error_log("❌ DB ERROR: " . $e->getMessage());
|
||||
printFailure("فشل في تسجيل العملية.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ====== 4. نجاح
|
||||
error_log("✅ Payment initiation successful.");
|
||||
printSuccess([
|
||||
"invoiceNumber" => $invoiceNumber,
|
||||
"operationNumber" => $operationNumber,
|
||||
"guid" => $guid
|
||||
]);
|
||||
|
||||
|
||||
// ====== دالة إرسال الطلب =====================
|
||||
function sendMtnApiRequest($requestName, $terminalId, $body)
|
||||
{
|
||||
$apiUrl = "https://cashmobile.mtnsyr.com:9000";
|
||||
$privateKey = openssl_pkey_get_private(file_get_contents("private_key.pem"));
|
||||
|
||||
// ✅ تحويل الـ body إلى JSON بدون فراغات أو أسطر
|
||||
$bodyJson = trim(stripslashes(json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS)), '"');
|
||||
//$bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
// ✅ توليد التوقيع
|
||||
// $bodyHash = hash('sha256', $bodyJson, true);
|
||||
error_log("📤 body before JSON: $bodyJson");
|
||||
openssl_sign($bodyJson, $signature, $privateKey, OPENSSL_ALGO_SHA256);
|
||||
$xSignature = base64_encode($signature);
|
||||
error_log("📤 body xSignature: $xSignature");
|
||||
// ✅ رؤوس الطلب
|
||||
$headers = [
|
||||
"Content-Type: application/json",
|
||||
"Accept-Language: $lang",
|
||||
"Request-Name: $requestName",
|
||||
"Subject: $terminalId",
|
||||
"X-Signature: $xSignature"
|
||||
];
|
||||
|
||||
$ch = curl_init($apiUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyJson);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// ✅ لوق داخلي
|
||||
error_log("🔐 Signature for $requestName: $xSignature");
|
||||
error_log("📤 Sent JSON: $bodyJson");
|
||||
|
||||
curl_close($ch);
|
||||
return json_decode($response, true);
|
||||
}
|
||||
@@ -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."]);
|
||||
}
|
||||
?>
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?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");
|
||||
$phone = filterRequest("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,
|
||||
phone = :phone,
|
||||
mtn_phone = :mtn_phone,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
");
|
||||
$upd->execute([
|
||||
':amount' => $amount,
|
||||
':phone' => $phone ?: null,
|
||||
':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, phone, amount, mtn_phone, status, created_at, updated_at)
|
||||
VALUES
|
||||
(:invoice_number, :user_id, :user_type, :phone, :amount, :mtn_phone, 'pending', NOW(), NOW())
|
||||
");
|
||||
$ins->execute([
|
||||
':invoice_number' => $invoiceNumber,
|
||||
':user_id' => $userId,
|
||||
':user_type' => $userType,
|
||||
':phone' => $phone ?: null,
|
||||
':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."]);
|
||||
}
|
||||
+106
@@ -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' => 'Finalization failed'];
|
||||
}
|
||||
}
|
||||
|
||||
// --- دوال مساعدة خاصة بالسائق ---
|
||||
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.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod) VALUES (:driverId, 'driver', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':driverId' => $driverId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet 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.');
|
||||
|
||||
// إضافة سجل محاسبي لمحفظة سفر
|
||||
$stmtSiro = $con->prepare("INSERT INTO siroWallet (passengerId, driverId, amount, paymentMethod) VALUES (:passengerId, 'passenger', :amount, :paymentMethod)");
|
||||
$stmtSiro->execute([':passengerId' => $passengerId, ':amount' => $amount, ':paymentMethod' => $paymentMethod]);
|
||||
if ($stmtSiro->rowCount() === 0) throw new Exception('Insert to siroWallet 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;
|
||||
}
|
||||
?>
|
||||
@@ -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 (!hash_equals($expectedToken, $receivedToken)) {
|
||||
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."]);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -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);
|
||||
}
|
||||
?>
|
||||
@@ -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."]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Migration: Add phone column to all invoice tables
|
||||
-- Allows direct phone lookup instead of S2S resolve_user
|
||||
-- Run: mysql -u root WalletIntaleqDB < migration_add_phone.sql
|
||||
|
||||
ALTER TABLE invoices_shamcash
|
||||
ADD COLUMN phone VARCHAR(20) AFTER driverID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_shamcash_passenger
|
||||
ADD COLUMN phone VARCHAR(20) AFTER passengerID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE cliq_invoices
|
||||
ADD COLUMN phone VARCHAR(20) AFTER user_type,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_sms
|
||||
ADD COLUMN phone VARCHAR(20) AFTER driverID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE invoices_sms_passenger
|
||||
ADD COLUMN phone VARCHAR(20) AFTER passengerID,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
|
||||
ALTER TABLE mtn_invoices
|
||||
ADD COLUMN phone VARCHAR(20) AFTER user_type,
|
||||
ADD INDEX idx_phone_status (phone, status);
|
||||
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
/**
|
||||
* Nabeh Payment Verification Endpoint
|
||||
*
|
||||
* Simplified: uses phone directly to find pending invoice (no S2S resolve_user).
|
||||
* Added Cliq AI verification with receipt image.
|
||||
*
|
||||
* ===============================
|
||||
* INPUT (JSON body)
|
||||
* ===============================
|
||||
* phone (required) — User's phone number
|
||||
* payment_method (req) — shamcash / cliq / sms / mtn
|
||||
* receipt_image (opt) — Receipt screenshot for AI verification
|
||||
* image_mime_type (opt) — Default: image/jpeg
|
||||
*
|
||||
* ===============================
|
||||
* FLOW
|
||||
* ===============================
|
||||
* 1. Auth via jwtconnect.php (X-API-Key → NABEH_API_KEY)
|
||||
* 2. Find latest pending invoice by phone + payment_method
|
||||
* 3. If shamcash/cliq + receipt_image → Gemini AI verification
|
||||
* AI confirms → update status → finalize deposit → return success
|
||||
* 4. Otherwise return invoice status
|
||||
*
|
||||
* Auth: X-API-Key header → NABEH_API_KEY (via jwtconnect.php Path 5)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
require_once __DIR__ . '/../GeminiAi.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['status' => 'failure', 'message' => 'Method not allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = json_decode($raw, true) ?: $_POST;
|
||||
|
||||
$phone = preg_replace('/\D+/', '', $data['phone'] ?? '');
|
||||
$paymentMethod = strtolower(trim($data['payment_method'] ?? ''));
|
||||
$receiptImage = $data['receipt_image'] ?? '';
|
||||
$imageMimeType = $data['image_mime_type'] ?? 'image/jpeg';
|
||||
|
||||
if (empty($phone)) {
|
||||
printFailure('phone is required');
|
||||
exit;
|
||||
}
|
||||
|
||||
$paymentMethod = $paymentMethod ?: 'shamcash';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// HELPER: find pending invoice by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
function findPendingByPhone(PDO $con, string $table, string $phone, string $orderCol = 'created_at'): ?array
|
||||
{
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, amount, status, created_at
|
||||
FROM $table
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY $orderCol DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$phone]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function findLastCompletedByPhone(PDO $con, string $table, string $phone): ?array
|
||||
{
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, amount, status, created_at
|
||||
FROM $table
|
||||
WHERE phone = ? AND status = 'completed'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$phone]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SHAMCASH — AI Verification
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'shamcash') {
|
||||
$invoice = findPendingByPhone($con, 'invoices_shamcash', $phone);
|
||||
|
||||
if (!$invoice) {
|
||||
$lastCompleted = findLastCompletedByPhone($con, 'invoices_shamcash', $phone);
|
||||
if ($lastCompleted) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => 'آخر فاتورة لديك مكتملة بالفعل.',
|
||||
'invoice' => $lastCompleted,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'لا توجد فاتورة معلقة. يرجى إنشاء فاتورة عبر تطبيق Siro أولاً.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($receiptImage)) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'requires_image' => true,
|
||||
'message' => "تم العثور على فاتورة رقم {$invoice['invoice_number']} بمبلغ {$invoice['amount']} ل.س. يرجى إرسال صورة الإيصال.",
|
||||
'invoice' => $invoice,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── AI verify ───────────────────────────────────────────
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
if (empty($geminiKey)) {
|
||||
printFailure('AI verification service not configured');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment(
|
||||
$invoice['invoice_number'],
|
||||
$invoice['amount'],
|
||||
'ShamCash',
|
||||
'',
|
||||
$receiptImage
|
||||
);
|
||||
|
||||
if (!empty($aiResult['verified'])) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("
|
||||
UPDATE invoices_shamcash
|
||||
SET status = 'processing'
|
||||
WHERE id = ? AND status = 'pending'
|
||||
");
|
||||
$upd->execute([$invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
require_once __DIR__ . '/../shamcash/finalize_deposit.php';
|
||||
$finalized = finalizeShamCashDeposit($con, $invoice['id']);
|
||||
if ($finalized) {
|
||||
$con->commit();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => '✅ تم التحقق من عملية الدفع بنجاح! تم تحديث رصيد حسابك.',
|
||||
'invoice' => [
|
||||
'invoice_number' => $invoice['invoice_number'],
|
||||
'amount' => $invoice['amount'],
|
||||
'status' => 'completed',
|
||||
],
|
||||
'ai_reason' => $aiResult['reason'] ?? null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Verification passed but wallet update failed. Contact support.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'These funds have already been credited.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? 'لم يتم التأكيد';
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => "⚠️ $reason",
|
||||
'ai_reason' => $reason,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Nabeh ShamCash AI] " . $e->getMessage());
|
||||
printFailure('AI verification service error');
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CLIQ — AI Verification (same pattern as ShamCash)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'cliq') {
|
||||
$invoice = findPendingByPhone($con, 'cliq_invoices', $phone);
|
||||
|
||||
if (!$invoice) {
|
||||
$lastCompleted = findLastCompletedByPhone($con, 'cliq_invoices', $phone);
|
||||
if ($lastCompleted) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => 'آخر فاتورة لديك مكتملة بالفعل.',
|
||||
'invoice' => $lastCompleted,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'لا توجد فاتورة معلقة. يرجى إنشاء فاتورة عبر تطبيق Siro أولاً.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($receiptImage)) {
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'requires_image' => true,
|
||||
'message' => "تم العثور على فاتورة رقم {$invoice['invoice_number']} بمبلغ {$invoice['amount']} دينار. يرجى إرسال صورة الإيصال.",
|
||||
'invoice' => $invoice,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── AI verify ───────────────────────────────────────────
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
if (empty($geminiKey)) {
|
||||
printFailure('AI verification service not configured');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$gemini = new GeminiAi($geminiKey);
|
||||
$aiResult = $gemini->verifyPayment(
|
||||
$invoice['invoice_number'],
|
||||
$invoice['amount'],
|
||||
'Cliq',
|
||||
'',
|
||||
$receiptImage
|
||||
);
|
||||
|
||||
if (!empty($aiResult['verified'])) {
|
||||
$con->beginTransaction();
|
||||
$upd = $con->prepare("
|
||||
UPDATE cliq_invoices
|
||||
SET status = 'completed', updated_at = NOW()
|
||||
WHERE id = ? AND status = 'pending'
|
||||
");
|
||||
$upd->execute([$invoice['id']]);
|
||||
|
||||
if ($upd->rowCount() > 0) {
|
||||
require_once __DIR__ . '/../cliq/finalize_payment.php';
|
||||
$finalized = finalizeClickPayment($con, $invoice['id']);
|
||||
if ($finalized['success']) {
|
||||
$con->commit();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => true,
|
||||
'message' => '✅ تم التحقق من عملية الدفع بنجاح! تم تحديث رصيد حسابك.',
|
||||
'invoice' => [
|
||||
'invoice_number' => $invoice['invoice_number'],
|
||||
'amount' => $invoice['amount'],
|
||||
'status' => 'completed',
|
||||
],
|
||||
'ai_reason' => $aiResult['reason'] ?? null,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Verification passed but wallet update failed. Contact support.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => 'These funds have already been credited.',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$reason = $aiResult['reason'] ?? 'لم يتم التأكيد';
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => false,
|
||||
'message' => "⚠️ $reason",
|
||||
'ai_reason' => $reason,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("[Nabeh Cliq AI] " . $e->getMessage());
|
||||
printFailure('AI verification service error');
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SMS / SYRIATEL — Status query by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'sms' || $paymentMethod === 'syriatel') {
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, user_phone AS method_phone, amount, status, created_at, ? AS payment_method
|
||||
FROM invoices_sms
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$paymentMethod, $phone]);
|
||||
$invoices = $stmt->fetchAll();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => !empty($invoices),
|
||||
'message' => empty($invoices) ? 'لا توجد فواتير معلقة.' : null,
|
||||
'invoices' => $invoices,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MTN — Status query by phone
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if ($paymentMethod === 'mtn') {
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, invoice_number, mtn_phone AS method_phone, amount, status,
|
||||
mtn_transaction_id AS transaction_id, created_at, updated_at AS paid_at, ? AS payment_method
|
||||
FROM mtn_invoices
|
||||
WHERE phone = ? AND status = 'pending'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
");
|
||||
$stmt->execute([$paymentMethod, $phone]);
|
||||
$invoices = $stmt->fetchAll();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'verified' => !empty($invoices),
|
||||
'message' => empty($invoices) ? 'لا توجد فواتير معلقة.' : null,
|
||||
'invoices' => $invoices,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// UNKNOWN METHOD
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
printFailure("Invalid payment method: $paymentMethod");
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
//addPassengersWallet.php
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
$balance = filterRequest("balance");
|
||||
$token = filterRequest("token");
|
||||
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// Retrieve token details from the database securely and lock the row
|
||||
$stmt = $con->prepare("SELECT * FROM payment_tokens_passenger WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||
$stmt->execute([':token' => $token]);
|
||||
$tokenData = $stmt->fetch();
|
||||
|
||||
if ($tokenData) {
|
||||
// Insert into passengerWallet securely using prepared statements
|
||||
$sql = "INSERT INTO `passengerWallet` (`passenger_id`, `balance`) VALUES (:passenger_id, :balance)";
|
||||
$stmtInsert = $con->prepare($sql);
|
||||
$stmtInsert->execute([':passenger_id' => $passenger_id, ':balance' => $balance]);
|
||||
|
||||
if ($stmtInsert->rowCount() > 0) {
|
||||
// Mark the token as used
|
||||
$updateTokenStmt = $con->prepare("UPDATE payment_tokens_passenger SET isUsed = TRUE WHERE id = :tokenID");
|
||||
$updateTokenStmt->execute([':tokenID' => $tokenData['id']]);
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Wallet record created successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to create wallet record");
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Invalid or already used token");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[passengerWallet/add] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
//addPaymentTokenPassenger.php
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
// Check if required fields are present
|
||||
if ($passengerId === null || $amount === null) {
|
||||
printFailure("Missing required fields: passengerId and amount must be provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Generate the token using current time
|
||||
$token = generateSecureToken($passengerId, $amount, date('Y-m-d H:i:s', time()));
|
||||
|
||||
// Store the token in the database, using NOW() for dateCreated
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens_passenger (token, passengerId, dateCreated, amount) VALUES (?, ?, NOW(), ?)");
|
||||
|
||||
try {
|
||||
$stmt->execute([$token, $passengerId, $amount]);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
printSuccess($token);
|
||||
} else {
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("[addPaymentTokenPassenger] " . $e->getMessage());
|
||||
printFailure("Database error");
|
||||
}
|
||||
|
||||
// Rest of your code including the generateSecureToken function...
|
||||
|
||||
// Rest of your code including the generateSecureToken function...
|
||||
|
||||
function generateSecureToken($passengerId, $amount, $dateCreated) {
|
||||
global $secretKey;
|
||||
// Concatenate the parameters
|
||||
$data = $passengerId . $amount . $dateCreated;
|
||||
|
||||
// Add the secret key from the environment variable
|
||||
$data .= $secretKey;
|
||||
|
||||
// Generate a hash
|
||||
$hash = hash('sha256', $data);
|
||||
|
||||
// Add some randomness
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
|
||||
// Combine hash and random bytes
|
||||
$token = $hash . $randomBytes;
|
||||
|
||||
// Truncate to a reasonable length (e.g., 64 characters)
|
||||
return substr($token, 0, 64);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* add_s2s_debt.php — Payment Server Endpoint
|
||||
*
|
||||
* Inserts passenger wallet credit/debit records (debt/penalty).
|
||||
* Authenticated via X-S2S-Api-Key header matching the S2S_SHARED_KEY environment variable.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$passengerID = filterRequest("passengerID");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
if (empty($passengerID) || !isset($amount)) {
|
||||
printFailure("Missing required parameters: passengerID, amount");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
$sql = "INSERT INTO `passengerWallet` (
|
||||
`passenger_id`,
|
||||
`balance`
|
||||
) VALUES (
|
||||
:passengerID,
|
||||
:amount
|
||||
);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([
|
||||
':passengerID' => $passengerID,
|
||||
':amount' => $amount
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$con->commit();
|
||||
printSuccess("Record saved successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("add_s2s_debt: " . $e->getMessage()); // logged server-side only
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
|
||||
$id = filterRequest("id");
|
||||
|
||||
$sql = "DELETE FROM `passengerWallet` WHERE `id` = '$id'";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess($message = "Wallet record deleted successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "Failed to delete wallet record");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
passengerWallet.`id`,
|
||||
passengerWallet.`passenger_id`,
|
||||
SUM(passengerWallet.balance) AS total,
|
||||
passengers.first_name,
|
||||
passengers.last_name,
|
||||
passengers.phone,
|
||||
passengers.email
|
||||
FROM
|
||||
`passengerWallet`
|
||||
LEFT JOIN passengers ON passengers.id = passengerWallet.passenger_id
|
||||
GROUP BY
|
||||
passenger_id";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
`id`,
|
||||
`passenger_id`,
|
||||
`balance`,
|
||||
`created_at`,
|
||||
`updated_at`,
|
||||
(
|
||||
SELECT
|
||||
SUM(balance)
|
||||
FROM
|
||||
passengerWallet
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'
|
||||
) AS total
|
||||
FROM
|
||||
`passengerWallet`
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'
|
||||
GROUP BY
|
||||
`passenger_id`,
|
||||
`id`;";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
passengerWallet.`id`,
|
||||
passengerWallet.balance,
|
||||
passengerWallet.`created_at`
|
||||
FROM
|
||||
`passengerWallet`
|
||||
WHERE
|
||||
passenger_id = '$passenger_id'AND created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
|
||||
ORDER BY
|
||||
`passengerWallet`.`id`
|
||||
DESC";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
$sql = "SELECT
|
||||
COALESCE(dummy.passenger_id, '$passenger_id') AS passenger_id,
|
||||
COALESCE(SUM(pw.balance), 0) AS total, -- Adjust column to represent payments
|
||||
COALESCE(p.first_name, '') AS first_name,
|
||||
COALESCE(p.last_name, '') AS last_name,
|
||||
COALESCE(p.phone, '') AS phone
|
||||
FROM
|
||||
(SELECT '$passenger_id' AS passenger_id) AS dummy
|
||||
LEFT JOIN `passengerWallet` pw ON pw.passenger_id = dummy.passenger_id
|
||||
LEFT JOIN passengers p ON p.id = dummy.passenger_id
|
||||
GROUP BY
|
||||
dummy.passenger_id, p.first_name, p.last_name, p.phone
|
||||
LIMIT 0, 25;
|
||||
";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Fetch the record
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
printSuccess( $row);
|
||||
|
||||
}
|
||||
else{
|
||||
// Print a failure message
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../jwtconnect.php';
|
||||
|
||||
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
|
||||
|
||||
if (empty($providedKey) || $providedKey !== getenv('S2S_SHARED_KEY')) {
|
||||
http_response_code(401);
|
||||
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$passenger_id = filterRequest("passenger_id");
|
||||
|
||||
if (empty($passenger_id)) {
|
||||
printFailure("Missing required parameter: passenger_id");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmtTotalWallet = $con->prepare("
|
||||
SELECT COALESCE(SUM(balance), 0)
|
||||
FROM `passengerWallet`
|
||||
WHERE passenger_id = :passenger_id
|
||||
");
|
||||
$stmtTotalWallet->execute([':passenger_id' => $passenger_id]);
|
||||
$totalWallet = (float)($stmtTotalWallet->fetchColumn() ?: 0.0);
|
||||
|
||||
printSuccess([
|
||||
"totalWallet" => $totalWallet
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log("[get_s2s_passenger_wallet] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
// process_wait_compensation.php
|
||||
// يوضع هذا الملف على سيرفر المدفوعات (Payment Server)
|
||||
|
||||
include "../../connect.php"; // تأكد من مسار الاتصال
|
||||
|
||||
// 1. استقبال البيانات
|
||||
$rideId = filterRequest("ride_id");
|
||||
$driverId = filterRequest("driver_id");
|
||||
$passengerId = filterRequest("passenger_id");
|
||||
$amount = filterRequest("amount"); // المبلغ الموجب (للسائق)
|
||||
$amountPassenger= filterRequest("amount_passenger"); // المبلغ السالب (للراكب)
|
||||
$tokenDriver = filterRequest("token_driver");
|
||||
$tokenPassenger = filterRequest("token_passenger");
|
||||
$paymentMethod = "wait-cancel"; // أو يمكن استقباله من التطبيق
|
||||
|
||||
if (!$rideId || !$driverId || !$passengerId || !$amount || !$tokenDriver || !$tokenPassenger) {
|
||||
printFailure("Missing parameters");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 🔥 بدء المعاملة المالية (Transaction)
|
||||
$con->beginTransaction();
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 1: التحقق من التوكنات (Security Check)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// أ) فحص توكن السائق (مع FOR UPDATE)
|
||||
$stmtCheckD = $con->prepare("SELECT id FROM payment_tokens WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||
$stmtCheckD->execute([$tokenDriver]);
|
||||
$tokenDriverData = $stmtCheckD->fetch();
|
||||
|
||||
if (!$tokenDriverData) {
|
||||
throw new Exception("Invalid or used Driver Token");
|
||||
}
|
||||
|
||||
// ب) فحص توكن الراكب (مع FOR UPDATE)
|
||||
$stmtCheckP = $con->prepare("SELECT id FROM payment_tokens_passenger WHERE token = ? AND isUsed = FALSE FOR UPDATE");
|
||||
$stmtCheckP->execute([$tokenPassenger]);
|
||||
$tokenPassengerData = $stmtCheckP->fetch();
|
||||
|
||||
if (!$tokenPassengerData) {
|
||||
throw new Exception("Invalid or used Passenger Token");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 2: إضافة سجل النقاط (paymentsDriverPoints)
|
||||
// ---------------------------------------------------------
|
||||
// هذا الجدول يبدو أنه "سجل العمليات" الرئيسي
|
||||
$sqlPoints = "INSERT INTO `paymentsDriverPoints` (`amount`, `payment_method`, `driverID`) VALUES (?, ?, ?)";
|
||||
$stmtPoints = $con->prepare($sqlPoints);
|
||||
$stmtPoints->execute([$amount, $paymentMethod, $driverId]);
|
||||
|
||||
// نحصل على ID العملية لنربطه بالمحفظة
|
||||
$paymentRecordID = $con->lastInsertId();
|
||||
|
||||
if ($stmtPoints->rowCount() == 0) {
|
||||
throw new Exception("Failed to insert into paymentsDriverPoints");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 3: إضافة الرصيد لمحفظة السائق (driverWallet)
|
||||
// ---------------------------------------------------------
|
||||
// نستخدم $paymentRecordID كمرجع للعملية
|
||||
$sqlWalletD = "INSERT INTO `driverWallet` (`driverID`, `paymentID`, `amount`, `paymentMethod`) VALUES (?, ?, ?, ?)";
|
||||
$stmtWalletD = $con->prepare($sqlWalletD);
|
||||
$stmtWalletD->execute([$driverId, $paymentRecordID, $amount, $paymentMethod]);
|
||||
|
||||
// حرق توكن السائق
|
||||
$con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE id = ?")->execute([$tokenDriverData['id']]);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// الخطوة 4: خصم الرصيد من محفظة الراكب (passengerWallet)
|
||||
// ---------------------------------------------------------
|
||||
$sqlWalletP = "INSERT INTO `passengerWallet` (`passenger_id`, `balance`) VALUES (?, ?)";
|
||||
$stmtWalletP = $con->prepare($sqlWalletP);
|
||||
$stmtWalletP->execute([$passengerId, $amountPassenger]);
|
||||
|
||||
// حرق توكن الراكب
|
||||
$con->prepare("UPDATE payment_tokens_passenger SET isUsed = TRUE WHERE id = ?")->execute([$tokenPassengerData['id']]);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// إتمام العملية (Commit)
|
||||
// ---------------------------------------------------------
|
||||
$con->commit();
|
||||
printSuccess("Compensation processed successfully");
|
||||
|
||||
} catch (Exception $e) {
|
||||
// في حال حدوث أي خطأ، يتم التراجع عن كل العمليات السابقة
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[process_wait_compensation] " . $e->getMessage());
|
||||
printFailure("Transaction Failed");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
<?php
|
||||
include "../../connect.php";
|
||||
$id = filterRequest("id");
|
||||
$balance = filterRequest("balance");
|
||||
|
||||
$sql = "UPDATE `passengerWallet` SET `balance` = '$balance' WHERE `id` = '$id'";
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
printSuccess($message = "Wallet record updated successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
printFailure($message = "Failed to update wallet record");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
// 1. احصل على AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1"); // ضع API Key الخاص بك هنا
|
||||
$email= filterRequest("amount");
|
||||
$first_name= filterRequest("first_name");
|
||||
$last_name= filterRequest("last_name");
|
||||
$phone_number= filterRequest("phone_number");
|
||||
$amount= filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
// printResponse("AUTH TOKEN RESPONSE", $response);
|
||||
|
||||
$auth_token = $response->token ?? null;
|
||||
if (!$auth_token) {
|
||||
die("❌ فشل الحصول على AUTH TOKEN!");
|
||||
}
|
||||
// $amount=$amount*100;
|
||||
// 2. أنشئ الطلب ORDER
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid(),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
// printResponse("ORDER RESPONSE", $response);
|
||||
|
||||
$order_id = $response->id ?? null;
|
||||
if (!$order_id) {
|
||||
die("❌ فشل إنشاء الطلب!");
|
||||
}
|
||||
$integration_id=getenv("paymobIntegratedIdCard");
|
||||
// 3. احصل على Payment Key
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" =>$first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "shobra",
|
||||
"street" => "Test St.",
|
||||
"building" => "1",
|
||||
"apartment" => "10",
|
||||
"floor" => "2",
|
||||
"postal_code" => "12345",
|
||||
"shipping_method"=> 'card'
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id, // ضع الـ Integration ID الصحيح
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
// printResponse("PAYMENT TOKEN RESPONSE", $response);
|
||||
|
||||
$payment_token = $response->token ?? null;
|
||||
if (!$payment_token) {
|
||||
die("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
}
|
||||
|
||||
// 4. إنشاء IFRAME URL
|
||||
$iframe_id = "837992"; // ضع الـ Iframe ID الصحيح
|
||||
$iframe_url = "https://accept.paymob.com/api/acceptance/iframes/$iframe_id?payment_token=$payment_token";
|
||||
if($payment_token){
|
||||
|
||||
printSuccess($iframe_url);
|
||||
}
|
||||
// دالة لطلب API عبر CURL
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return json_decode($response);
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/payment_verification.log"); // Define log file path
|
||||
|
||||
// Function to write to error log
|
||||
function logError($step, $message, $data = null) {
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logEntry = "[{$timestamp}] STEP {$step}: {$message}";
|
||||
|
||||
if ($data !== null) {
|
||||
$logEntry .= " | Data: " . json_encode($data);
|
||||
}
|
||||
|
||||
// Ensure log directory exists
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
// Append to log file
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Also log to PHP error log for server monitoring
|
||||
// error_log("PAYMENT_VERIFICATION: {$logEntry}");
|
||||
}
|
||||
|
||||
// Receive parameters from GET request
|
||||
$user_id = filterRequest("user_id");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
|
||||
// Log initial request
|
||||
// logError("0", "Request received", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
// Validate user_id and passengerId
|
||||
if (!$user_id || !$passengerId) {
|
||||
// logError("1", "Invalid parameters", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
printFailure("Invalid user ID or passenger ID.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Get the latest successful payment
|
||||
// logError("1", "Querying latest payment", ["user_id" => $user_id]);
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLog WHERE user_id = :user_id AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1");
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("1", "No payment found", ["user_id" => $user_id]);
|
||||
printFailure("No payment data found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("1", "Payment found", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "status" => $payment['status'],
|
||||
// "amount" => $payment['amount']/100 ?? 'unknown'
|
||||
// ]);
|
||||
|
||||
// Step 2: Check payment status
|
||||
if ($payment['status'] != 1) {
|
||||
// logError("2", "Payment not successful", ["status" => $payment['status']]);
|
||||
printFailure("Payment is not successful yet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("2", "Payment status verified", ["status" => $payment['status']]);
|
||||
|
||||
$amount = $payment['amount']/100; // Paid amount
|
||||
|
||||
// Step 3: Calculate bonus based on the paid amount
|
||||
// logError("3", "Calculating bonus", ["amount" => $amount]);
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
if ($finalAmount <= 0) {
|
||||
// logError("3", "Bonus calculation failed", [
|
||||
// "original_amount" => $amount,
|
||||
// "calculated_amount" => $finalAmount
|
||||
// ]);
|
||||
printFailure("Invalid amount for bonus calculation.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("3", "Bonus calculated", [
|
||||
// "original_amount" => $amount,
|
||||
// "final_amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
// // Step 4: Generate payment token
|
||||
// logError("4", "Generating payment token", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
// // Step 5: Add balance to passenger's wallet
|
||||
// logError("5", "Adding balance to passenger wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$walletResult = addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
|
||||
if (!$walletResult || !isset($walletResult['status']) || $walletResult['status'] != "success") {
|
||||
// logError("5", "Failed to add balance to passenger wallet", $walletResult);
|
||||
printFailure("Payment verified, but failed to add balance to passenger wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("5", "Balance added to passenger wallet", $walletResult);
|
||||
|
||||
// Step 6: Add balance to Siro wallet
|
||||
// logError("6", "Adding balance to Siro wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount,
|
||||
// "paymentMethod" => $paymentMethod
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
$siroWalletResult = addToSiroWallet($passengerId, $amount, $paymentMethod);
|
||||
|
||||
if (!$siroWalletResult || !isset($siroWalletResult['status']) || $siroWalletResult['status'] != "success") {
|
||||
// logError("6", "Failed to add balance to Siro wallet", $siroWalletResult);
|
||||
printFailure("Payment verified, but failed to add balance to Siro wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("6", "Balance added to Siro wallet", $siroWalletResult);
|
||||
|
||||
// // Final success
|
||||
// logError("7", "Process completed successfully", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "amount" => $finalAmount,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
printSuccess( "Payment data saved successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
logError("ERROR", "Database error: " . $e->getMessage());
|
||||
printFailure("Database error occurred.");
|
||||
} catch (Exception $e) {
|
||||
logError("ERROR", "General error: " . $e->getMessage());
|
||||
printFailure("An error occurred during payment verification.");
|
||||
}
|
||||
|
||||
// 🎯 Function to generate payment token with error logging
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
|
||||
$postData = [
|
||||
'passengerId' => $passengerId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("4.1", "cURL error in token generation", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("4.2", "HTTP error in token generation", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !isset($data['message'])) {
|
||||
logError("4.3", "Invalid response format in token generation", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data['message']; // ✅ Return token
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to passenger's wallet with error logging
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'passenger_id' => $passengerId,
|
||||
'balance' => $amount,
|
||||
'token' => $token
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("5.1", "cURL error in passenger wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("5.2", "HTTP error in passenger wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("5.3", "Invalid response format in passenger wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to Siro wallet with error logging
|
||||
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod) {
|
||||
|
||||
|
||||
// Generate a new token specifically for the Siro wallet
|
||||
$siroToken = generatePaymentToken($passengerId, $amount);
|
||||
|
||||
if (!$siroToken) {
|
||||
logError("6.0.1", "Failed to generate Siro token");
|
||||
return null;
|
||||
}
|
||||
|
||||
logError("6.0.2", "Generated new Siro token", [
|
||||
"token_length" => ($siroToken)
|
||||
]);
|
||||
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $siroToken, // Use the new Siro-specific token
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("6.1", "cURL error in Siro wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("6.2", "HTTP error in Siro wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("6.3", "Invalid response format in Siro wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
|
||||
// 🎯 Function to calculate bonus
|
||||
function calculateBonus($amount) {
|
||||
logError("3.1", "Bonus calculation input", ["amount" => $amount]);
|
||||
|
||||
$result = 0;
|
||||
if ($amount == 100) $result = 100;
|
||||
else if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
|
||||
logError("3.2", "Bonus calculation result", [
|
||||
"input" => $amount,
|
||||
"output" => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
Binary file not shown.
@@ -0,0 +1,100 @@
|
||||
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
// 1. احصل على AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1"); // ضع API Key الخاص بك هنا
|
||||
$email= filterRequest("amount");
|
||||
$first_name= filterRequest("first_name");
|
||||
$last_name= filterRequest("last_name");
|
||||
$phone_number= filterRequest("phone_number");
|
||||
$amount= filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
// printResponse("AUTH TOKEN RESPONSE", $response);
|
||||
|
||||
$auth_token = $response->token ?? null;
|
||||
if (!$auth_token) {
|
||||
die("❌ فشل الحصول على AUTH TOKEN!");
|
||||
}
|
||||
$amount=$amount*100;
|
||||
// 2. أنشئ الطلب ORDER
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid(),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
// printResponse("ORDER RESPONSE", $response);
|
||||
|
||||
$order_id = $response->id ?? null;
|
||||
if (!$order_id) {
|
||||
die("❌ فشل إنشاء الطلب!");
|
||||
}
|
||||
$integration_id=getenv("paymobIntegratedIdCardDriver");
|
||||
// 3. احصل على Payment Key
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" =>$first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "shobra",
|
||||
"street" => "Test St.",
|
||||
"building" => "1",
|
||||
"apartment" => "10",
|
||||
"floor" => "2",
|
||||
"postal_code" => "12345",
|
||||
"shipping_method"=> 'card'
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id, // ضع الـ Integration ID الصحيح
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
// printResponse("PAYMENT TOKEN RESPONSE", $response);
|
||||
|
||||
$payment_token = $response->token ?? null;
|
||||
if (!$payment_token) {
|
||||
die("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
}
|
||||
|
||||
// 4. إنشاء IFRAME URL
|
||||
$iframe_id = "837992"; // ضع الـ Iframe ID الصحيح
|
||||
$iframe_url = "https://accept.paymob.com/api/acceptance/iframes/$iframe_id?payment_token=$payment_token";
|
||||
if($payment_token){
|
||||
|
||||
printSuccess($iframe_url);
|
||||
}
|
||||
// دالة لطلب API عبر CURL
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return json_decode($response);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// 1️⃣ AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1");
|
||||
$integration_id = getenv("paymobIntegratedIdDriverWallet"); // 🔁 تأكد أنه خاص بالسائق
|
||||
|
||||
$email = filterRequest("email");
|
||||
$first_name = filterRequest("first_name");
|
||||
$last_name = filterRequest("last_name");
|
||||
$phone_number = filterRequest("phone_number"); // هاتف السائق
|
||||
$wallet_phone = '+2'.$phone_number;
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
$auth_token = $response->token ?? null;
|
||||
|
||||
if (!$auth_token) {
|
||||
error_log("❌ AUTH TOKEN retrieval failed!");
|
||||
die("❌ AUTH TOKEN retrieval failed!");
|
||||
}
|
||||
$amount=$amount*100;
|
||||
// 2️⃣ ORDER CREATE
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid("DRV_"),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
$order_id = $response->id ?? null;
|
||||
|
||||
if (!$order_id) {
|
||||
error_log("❌ Failed to create order for driver wallet!");
|
||||
die("❌ Failed to create order for driver wallet!");
|
||||
}
|
||||
|
||||
// 3️⃣ PAYMENT KEY
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" => $first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "Nasr City",
|
||||
"street" => "Driver Zone",
|
||||
"building" => "5",
|
||||
"apartment" => "D1",
|
||||
"floor" => "1",
|
||||
"postal_code" => "11765",
|
||||
"shipping_method" => "driver_wallet"
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
$payment_token = $response->token ?? null;
|
||||
|
||||
if (!$payment_token) {
|
||||
error_log("❌ Failed to get PAYMENT TOKEN for driver!");
|
||||
die("❌ Failed to get PAYMENT TOKEN for driver!");
|
||||
}
|
||||
|
||||
// 4️⃣ Final Step: Pay with Wallet
|
||||
$redirect_url = payWithWallet($payment_token, $wallet_phone);
|
||||
if ($redirect_url) {
|
||||
printSuccess($redirect_url);
|
||||
error_log("✅ redirect_url (driver): " . $redirect_url);
|
||||
} else {
|
||||
error_log("❌ Driver wallet payment failed!");
|
||||
printFailure("Payment verified, but failed to redirect.");
|
||||
}
|
||||
|
||||
|
||||
// 🔁 Shared helper functions
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
return json_decode($response);
|
||||
}
|
||||
|
||||
function payWithWallet($paymentToken, $walletPhone)
|
||||
{
|
||||
$url = "https://accept.paymob.com/api/acceptance/payments/pay";
|
||||
$data = [
|
||||
"source" => [
|
||||
"identifier" => $walletPhone,
|
||||
"subtype" => "WALLET"
|
||||
],
|
||||
"payment_token" => $paymentToken
|
||||
];
|
||||
$response = callAPI("POST", $url, json_encode($data));
|
||||
return $response->redirect_url ?? null;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
include "../../../jwtconnect.php";
|
||||
define('BASE_URL', 'https://wl.tripz-egypt.com/v1/main/ride');
|
||||
|
||||
try {
|
||||
$driverId = filterRequest('driverID');
|
||||
$user_id = filterRequest('user_id');
|
||||
$paymentMethod = filterRequest('paymentMethod');
|
||||
|
||||
if (empty($user_id) || empty($driverId)) {
|
||||
printFailure('Missing user_id or driverID.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1️⃣ تحقق من سجل الدفع خلال آخر دقيقتين
|
||||
$stmt = $con->prepare(
|
||||
'SELECT * FROM payment_log_driver
|
||||
WHERE user_id = :uid
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
|
||||
ORDER BY created_at DESC LIMIT 1'
|
||||
);
|
||||
$stmt->execute([':uid' => $user_id]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment || $payment['status'] != 1) {
|
||||
printFailure('No valid payment found.');
|
||||
exit;
|
||||
}
|
||||
|
||||
$originalAmount = floatval($payment['amount']);
|
||||
$bonus = match ((int)$originalAmount) {
|
||||
80 => 80.0,
|
||||
200 => 215.0,
|
||||
400 => 450.0,
|
||||
1000 => 1140.0,
|
||||
default => $originalAmount,
|
||||
};
|
||||
|
||||
// 2️⃣ توكن لـ DriverWallet
|
||||
$tokenDriver = generateToken($con, $driverId, $bonus);
|
||||
if (!$tokenDriver) {
|
||||
printFailure('Failed to generate token for driver wallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3️⃣ توكن مستقل لـ SiroWallet
|
||||
$tokenSiro = generateToken($con, $driverId, $originalAmount);
|
||||
if (!$tokenSiro) {
|
||||
printFailure('Failed to generate token for siro wallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4️⃣ Payment ID
|
||||
$paymentID = generatePaymentID($con, $driverId, $bonus, $paymentMethod);
|
||||
if (!$paymentID) {
|
||||
printFailure('Failed to generate payment ID.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5️⃣ Insert into driverWallet
|
||||
$insertDriver = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:driverID, :paymentID, :amount, :paymentMethod)");
|
||||
$insertDriver->execute([
|
||||
':driverID' => $driverId,
|
||||
':paymentID' => $paymentID,
|
||||
':amount' => $bonus,
|
||||
':paymentMethod' => $paymentMethod
|
||||
]);
|
||||
|
||||
if ($insertDriver->rowCount() === 0) {
|
||||
printFailure('Failed to insert into driverWallet.');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 6️⃣ Update tokenDriver to isUsed = TRUE
|
||||
$markTokenDriver = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenDriver->execute([':token' => $tokenDriver]);
|
||||
|
||||
// 7️⃣ Insert into siroWallet
|
||||
$insertSiro = $con->prepare("INSERT INTO siroWallet (driverId, passengerId, amount, paymentMethod, token, createdAt)
|
||||
VALUES (:driverId, :passengerId, :amount, :paymentMethod, :token, CURRENT_TIMESTAMP)");
|
||||
$insertSiro->execute([
|
||||
':driverId' => $driverId,
|
||||
':passengerId' => 'driver',
|
||||
':amount' => $originalAmount,
|
||||
':paymentMethod' => $paymentMethod,
|
||||
':token' => $tokenSiro
|
||||
]);
|
||||
|
||||
// 8️⃣ Update tokenSiro to isUsed = TRUE
|
||||
$markTokenSiro = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE token = :token");
|
||||
$markTokenSiro->execute([':token' => $tokenSiro]);
|
||||
|
||||
// 🎉 Success response
|
||||
printSuccess([
|
||||
'message' => 'Payment verified and all wallets updated successfully.',
|
||||
'amount' => $originalAmount,
|
||||
'bonus' => $bonus,
|
||||
'paymentID' => $paymentID,
|
||||
'tokenUsed' => [
|
||||
'driverWalletToken' => $tokenDriver,
|
||||
'siroWalletToken' => $tokenSiro
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
printFailure("Server error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// ───────────────────────────
|
||||
// FUNCTIONS
|
||||
// ───────────────────────────
|
||||
|
||||
function generateToken($con, $driverId, $amount): ?string {
|
||||
global $secretKey;
|
||||
|
||||
// نفس المنطق من سكربتك
|
||||
$data = $driverId . $amount . time();
|
||||
$data .= $secretKey;
|
||||
$hash = hash('sha256', $data);
|
||||
$randomBytes = bin2hex(random_bytes(16));
|
||||
$token = substr($hash . $randomBytes, 0, 64);
|
||||
// تخزين التوكن في قاعدة البيانات
|
||||
$stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount)
|
||||
VALUES (:token, :driverID, NOW(), :amount)");
|
||||
$stmt->execute([
|
||||
':token' => $token,
|
||||
':driverID' => $driverId,
|
||||
':amount' => $amount
|
||||
]);
|
||||
|
||||
return $stmt->rowCount() > 0 ? $token : null;
|
||||
}
|
||||
|
||||
function generatePaymentID($con, $driverId, $amount, $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;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
// paymob_payout.php
|
||||
// سكريبت بي ات بي لمعاملات Paymob Payout (محفظة وبنك) بدون تخزين في قاعدة البيانات
|
||||
|
||||
declare(strict_types=1);
|
||||
include '../../../jwtconnect.php'; // يعطيك $con، filterRequest(), printSuccess(), printFailure()
|
||||
|
||||
// 1) جلب باراميترات الطلب عبر filterRequest
|
||||
$driverId = filterRequest('driverID');
|
||||
$amount = filterRequest('amount');
|
||||
$method = filterRequest('method'); // 'wallet' أو 'bank'
|
||||
$msisdn = filterRequest('msisdn');
|
||||
$bankCard = filterRequest('bankCard'); // يُستعمل عند method == 'bank'
|
||||
$bankCode = filterRequest('bankCode'); // يُستعمل عند method == 'bank'
|
||||
|
||||
if (empty($driverId) || empty($amount) || empty($method)) {
|
||||
printFailure('Missing parameters');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2) جلب بيانات Paymob من البيئة (Environment Variables)
|
||||
$pmUser = getenv('payMobOutUserName');
|
||||
$pmPass = getenv('payMobOutPassword');
|
||||
$pmClientId = getenv('PAYMOBOUTCLIENT_ID'); // من static const pmobid
|
||||
$pmSecret = getenv('PAYMOBOUTCLIENTSECRET'); // من static const pmobsec
|
||||
|
||||
// 3) دالة للحصول على OAuth Token من Paymob
|
||||
function fetchPaymobToken(string $user, string $pass, string $cid, string $secret): ?string {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/o/token/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
|
||||
CURLOPT_POSTFIELDS => http_build_query([
|
||||
'grant_type' => 'password',
|
||||
'username' => $user,
|
||||
'password' => $pass,
|
||||
'client_id' => $cid,
|
||||
'client_secret' => $secret,
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
if (!$resp) return null;
|
||||
$data = json_decode($resp, true);
|
||||
return $data['access_token'] ?? null;
|
||||
}
|
||||
|
||||
$oauthToken = fetchPaymobToken($pmUser, $pmPass, $pmClientId, $pmSecret);
|
||||
if (!$oauthToken) {
|
||||
printFailure('Failed to retrieve Paymob token');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4) دوال صرف الأموال
|
||||
function disburseWallet(string $token, string $amt, string $msisdn): array {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/disburse/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer $token",
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'amount' => $amt,
|
||||
'issuer' => 'wallet',
|
||||
'msisdn' => $msisdn,
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
return $resp ? json_decode($resp, true) : [];
|
||||
}
|
||||
|
||||
function disburseBank(string $token, string $amt, string $card, string $code): array {
|
||||
$ch = curl_init('https://payouts.paymobsolutions.com/api/secure/disburse/');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer $token",
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'amount' => $amt,
|
||||
'issuer' => 'bank_card',
|
||||
'bank_card_number' => $card,
|
||||
'bank_code' => $code,
|
||||
'bank_transaction_type' => 'cash_transfer',
|
||||
]),
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
return $resp ? json_decode($resp, true) : [];
|
||||
}
|
||||
|
||||
// 5) استدعاء الدالة المناسبة وتنفيذ الصرف
|
||||
if ($method === 'wallet') {
|
||||
$result = disburseWallet($oauthToken, $amount, $msisdn);
|
||||
} else {
|
||||
$result = disburseBank($oauthToken, $amount, $bankCard, $bankCode);
|
||||
}
|
||||
|
||||
// 6) التحقق من نجاح الصرف وإرجاع النتيجة
|
||||
if (empty($result) || ($result['disbursement_status'] ?? '') !== 'successful') {
|
||||
printFailure('Disbursement failed');
|
||||
exit;
|
||||
}
|
||||
|
||||
// 7) إرجاع التوكن والنتيجة للعميل بدون تخزين في DB
|
||||
printSuccess( $result);
|
||||
?>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
//
|
||||
/*طباعة الرسائل لأغراض التصحيح
|
||||
error_log("🔐 Message used for HMAC: " . $message);
|
||||
error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
error_log("📩 Received HMAC: " . $received_hmac);
|
||||
error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
*/
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
$user_id='+2'. $user_id;
|
||||
$amount=$amount/100;
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO payment_log_driver (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
error_log("Payment data saved successfully" . $status);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
//
|
||||
/*طباعة الرسائل لأغراض التصحيح
|
||||
error_log("🔐 Message used for HMAC: " . $message);
|
||||
error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
error_log("📩 Received HMAC: " . $received_hmac);
|
||||
error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
*/
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
$user_id='+'. $user_id;
|
||||
$amount=$amount/100;
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO payment_log_driver (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
error_log("Payment data saved successfully" . $status);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
include "../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
// طباعة الرسائل لأغراض التصحيح
|
||||
// error_log("🔐 Message used for HMAC: " . $message);
|
||||
// error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
// error_log("📩 Received HMAC: " . $received_hmac);
|
||||
// error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
// error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO paymentsLog (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// 1. احصل على AUTH TOKEN
|
||||
$api_key = getenv("payMobApiKey1");
|
||||
$integration_id = getenv("paymobIntegratedIdWallet");
|
||||
$email = filterRequest("email");
|
||||
$first_name = filterRequest("first_name");
|
||||
$last_name = filterRequest("last_name");
|
||||
$phone_number = filterRequest("phone_number");
|
||||
$wallet_phone = filterRequest("phone_number");
|
||||
$amount = filterRequest("amount");
|
||||
|
||||
$auth_url = "https://accept.paymob.com/api/auth/tokens";
|
||||
$auth_data = json_encode(["api_key" => $api_key]);
|
||||
|
||||
$response = callAPI("POST", $auth_url, $auth_data);
|
||||
$auth_token = $response->token ?? null;
|
||||
if (!$auth_token) {
|
||||
error_log("❌ فشل الحصول على AUTH TOKEN!");
|
||||
die("❌ فشل الحصول على AUTH TOKEN!");
|
||||
}
|
||||
|
||||
// 2. أنشئ الطلب ORDER
|
||||
$order_url = "https://accept.paymob.com/api/ecommerce/orders";
|
||||
$order_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"delivery_needed" => false,
|
||||
"amount_cents" => $amount,
|
||||
"currency" => "EGP",
|
||||
"merchant_order_id" => uniqid(),
|
||||
"items" => []
|
||||
];
|
||||
|
||||
$response = callAPI("POST", $order_url, json_encode($order_data));
|
||||
$order_id = $response->id ?? null;
|
||||
if (!$order_id) {
|
||||
error_log("❌ فشل إنشاء الطلب!");
|
||||
die("❌ فشل إنشاء الطلب!");
|
||||
}
|
||||
// error_log("orde is" .$order_id);
|
||||
// 3. احصل على Payment Key
|
||||
|
||||
$payment_key_url = "https://accept.paymob.com/api/acceptance/payment_keys";
|
||||
$payment_key_data = [
|
||||
"auth_token" => $auth_token,
|
||||
"amount_cents" => $amount,
|
||||
"expiration" => 3600,
|
||||
"order_id" => $order_id,
|
||||
"billing_data" => [
|
||||
"first_name" => $first_name,
|
||||
"last_name" => $last_name,
|
||||
"email" => $email,
|
||||
"phone_number" => $phone_number,
|
||||
"country" => "EG",
|
||||
"city" => "Cairo",
|
||||
"state" => "shobra",
|
||||
"street" => "Test St.",
|
||||
"building" => "1",
|
||||
"apartment" => "10",
|
||||
"floor" => "2",
|
||||
"postal_code" => "12345",
|
||||
"shipping_method" => "wallet"
|
||||
],
|
||||
"currency" => "EGP",
|
||||
"integration_id" => $integration_id // إذا كان مضبوط
|
||||
];
|
||||
$response = callAPI("POST", $payment_key_url, json_encode($payment_key_data));
|
||||
$payment_token = $response->token ?? null;
|
||||
// error_log("payment_token is" .$payment_token);
|
||||
if (!$payment_token) {
|
||||
error_log("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
|
||||
die("❌ فشل الحصول على PAYMENT TOKEN!");
|
||||
}
|
||||
// error_log("phone wallet is ".$wallet_phone);
|
||||
// 4. الدفع عبر المحفظة Wallet
|
||||
$redirect_url = payWithWallet($payment_token, $wallet_phone);
|
||||
if ($redirect_url) {
|
||||
printSuccess($redirect_url);
|
||||
error_log("redirect_url is" .$redirect_url);
|
||||
} else {
|
||||
error_log("❌ فشل الدفع عبر المحفظة!");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
// die("❌ فشل الدفع عبر المحفظة!");
|
||||
}
|
||||
|
||||
// دالة لطلب API عبر CURL
|
||||
function callAPI($method, $url, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"]
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return json_decode($response);
|
||||
}
|
||||
|
||||
// الدالة الخاصة بالدفع بالمحفظة
|
||||
function payWithWallet($paymentToken, $walletPhone)
|
||||
{
|
||||
$url = "https://accept.paymob.com/api/acceptance/payments/pay";
|
||||
|
||||
$data = [
|
||||
"source" => [
|
||||
"identifier" => $walletPhone,
|
||||
"subtype" => "WALLET"
|
||||
],
|
||||
"payment_token" => $paymentToken
|
||||
];
|
||||
|
||||
// Log the full data being sent to Paymob
|
||||
// error_log("Data being sent to Paymob: " . json_encode($data));
|
||||
|
||||
$response = callAPI("POST", $url, json_encode($data));
|
||||
|
||||
// Log the full response for debugging
|
||||
// error_log("Payment response: " . print_r($response, true));
|
||||
|
||||
return $response->redirect_url ?? null;
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
define("BASE_URL", "https://wl.tripz-egypt.com/v1/main/ride");
|
||||
define("LOG_FILE", "../logs/payment_verification.log"); // Define log file path
|
||||
|
||||
// Function to write to error log
|
||||
function logError($step, $message, $data = null) {
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logEntry = "[{$timestamp}] STEP {$step}: {$message}";
|
||||
|
||||
if ($data !== null) {
|
||||
$logEntry .= " | Data: " . json_encode($data);
|
||||
}
|
||||
|
||||
// Ensure log directory exists
|
||||
$logDir = dirname(LOG_FILE);
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
// Append to log file
|
||||
file_put_contents(LOG_FILE, $logEntry . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Also log to PHP error log for server monitoring
|
||||
// error_log("PAYMENT_VERIFICATION: {$logEntry}");
|
||||
}
|
||||
|
||||
// Receive parameters from GET request
|
||||
$user_id = filterRequest("user_id");
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$paymentMethod = filterRequest("paymentMethod");
|
||||
|
||||
// Log initial request
|
||||
// logError("0", "Request received", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
// Validate user_id and passengerId
|
||||
if (!$user_id || !$passengerId) {
|
||||
// logError("1", "Invalid parameters", [
|
||||
// "user_id" => $user_id,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
printFailure("Invalid user ID or passenger ID.");
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Get the latest successful payment
|
||||
// logError("1", "Querying latest payment", ["user_id" => $user_id]);
|
||||
|
||||
$stmt = $con->prepare("SELECT * FROM paymentsLog WHERE user_id = :user_id AND created_at >= DATE_SUB(NOW(), INTERVAL 2 MINUTE)
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1");
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$payment) {
|
||||
logError("1", "No payment found", ["user_id" => $user_id]);
|
||||
printFailure("No payment data found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("1", "Payment found", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "status" => $payment['status'],
|
||||
// "amount" => $payment['amount']/100 ?? 'unknown'
|
||||
// ]);
|
||||
|
||||
// Step 2: Check payment status
|
||||
if ($payment['status'] != 1) {
|
||||
// logError("2", "Payment not successful", ["status" => $payment['status']]);
|
||||
printFailure("Payment is not successful yet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("2", "Payment status verified", ["status" => $payment['status']]);
|
||||
|
||||
$amount = $payment['amount']/100; // Paid amount
|
||||
|
||||
// Step 3: Calculate bonus based on the paid amount
|
||||
// logError("3", "Calculating bonus", ["amount" => $amount]);
|
||||
$finalAmount = calculateBonus($amount);
|
||||
|
||||
if ($finalAmount <= 0) {
|
||||
// logError("3", "Bonus calculation failed", [
|
||||
// "original_amount" => $amount,
|
||||
// "calculated_amount" => $finalAmount
|
||||
// ]);
|
||||
printFailure("Invalid amount for bonus calculation.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("3", "Bonus calculated", [
|
||||
// "original_amount" => $amount,
|
||||
// "final_amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
// // Step 4: Generate payment token
|
||||
// logError("4", "Generating payment token", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
// // Step 5: Add balance to passenger's wallet
|
||||
// logError("5", "Adding balance to passenger wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount
|
||||
// ]);
|
||||
|
||||
$walletResult = addToPassengerWallet($passengerId, $finalAmount, $token);
|
||||
|
||||
if (!$walletResult || !isset($walletResult['status']) || $walletResult['status'] != "success") {
|
||||
// logError("5", "Failed to add balance to passenger wallet", $walletResult);
|
||||
printFailure("Payment verified, but failed to add balance to passenger wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("5", "Balance added to passenger wallet", $walletResult);
|
||||
|
||||
// Step 6: Add balance to Siro wallet
|
||||
// logError("6", "Adding balance to Siro wallet", [
|
||||
// "passengerId" => $passengerId,
|
||||
// "amount" => $finalAmount,
|
||||
// "paymentMethod" => $paymentMethod
|
||||
// ]);
|
||||
|
||||
$token = generatePaymentToken($passengerId, $finalAmount);
|
||||
|
||||
if (!$token) {
|
||||
// logError("4", "Token generation failed");
|
||||
printFailure("Payment verified, but failed to generate token.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("4", "Token generated successfully", ["token_length" => strlen($token)]);
|
||||
|
||||
$siroWalletResult = addToSiroWallet($passengerId, $amount, $paymentMethod);
|
||||
|
||||
if (!$siroWalletResult || !isset($siroWalletResult['status']) || $siroWalletResult['status'] != "success") {
|
||||
// logError("6", "Failed to add balance to Siro wallet", $siroWalletResult);
|
||||
printFailure("Payment verified, but failed to add balance to Siro wallet.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// logError("6", "Balance added to Siro wallet", $siroWalletResult);
|
||||
|
||||
// // Final success
|
||||
// logError("7", "Process completed successfully", [
|
||||
// "payment_id" => $payment['id'] ?? 'unknown',
|
||||
// "amount" => $finalAmount,
|
||||
// "passengerId" => $passengerId
|
||||
// ]);
|
||||
|
||||
printSuccess( "Payment data saved successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
logError("ERROR", "Database error: " . $e->getMessage());
|
||||
printFailure("Database error occurred.");
|
||||
} catch (Exception $e) {
|
||||
logError("ERROR", "General error: " . $e->getMessage());
|
||||
printFailure("An error occurred during payment verification.");
|
||||
}
|
||||
|
||||
// 🎯 Function to generate payment token with error logging
|
||||
function generatePaymentToken($passengerId, $amount) {
|
||||
$url = BASE_URL . "/passengerWallet/addPaymentTokenPassenger.php";
|
||||
|
||||
$postData = [
|
||||
'passengerId' => $passengerId,
|
||||
'amount' => $amount
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("4.1", "cURL error in token generation", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("4.2", "HTTP error in token generation", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data || !isset($data['message'])) {
|
||||
logError("4.3", "Invalid response format in token generation", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data['message']; // ✅ Return token
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to passenger's wallet with error logging
|
||||
function addToPassengerWallet($passengerId, $amount, $token) {
|
||||
$url = BASE_URL . "/passengerWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'passenger_id' => $passengerId,
|
||||
'balance' => $amount,
|
||||
'token' => $token
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("5.1", "cURL error in passenger wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("5.2", "HTTP error in passenger wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("5.3", "Invalid response format in passenger wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
// 🎯 Function to add balance to Siro wallet with error logging
|
||||
|
||||
|
||||
function addToSiroWallet($passengerId, $amount, $paymentMethod) {
|
||||
|
||||
|
||||
// Generate a new token specifically for the Siro wallet
|
||||
$siroToken = generatePaymentToken($passengerId, $amount);
|
||||
|
||||
if (!$siroToken) {
|
||||
logError("6.0.1", "Failed to generate Siro token");
|
||||
return null;
|
||||
}
|
||||
|
||||
logError("6.0.2", "Generated new Siro token", [
|
||||
"token_length" => ($siroToken)
|
||||
]);
|
||||
|
||||
$url = BASE_URL . "/siroWallet/add.php";
|
||||
|
||||
$postData = [
|
||||
'amount' => $amount,
|
||||
'paymentMethod' => $paymentMethod,
|
||||
'passengerId' => $passengerId,
|
||||
'token' => $siroToken, // Use the new Siro-specific token
|
||||
'driverId' => 'passenger'
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
logError("6.1", "cURL error in Siro wallet update", [
|
||||
"error" => $curlError,
|
||||
"url" => $url
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($httpCode != 200) {
|
||||
logError("6.2", "HTTP error in Siro wallet update", [
|
||||
"http_code" => $httpCode,
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!$data) {
|
||||
logError("6.3", "Invalid response format in Siro wallet update", [
|
||||
"response" => $response
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data; // ✅ Return result
|
||||
}
|
||||
|
||||
|
||||
// 🎯 Function to calculate bonus
|
||||
function calculateBonus($amount) {
|
||||
logError("3.1", "Bonus calculation input", ["amount" => $amount]);
|
||||
|
||||
$result = 0;
|
||||
if ($amount == 100) $result = 100;
|
||||
else if ($amount == 200) $result = 215;
|
||||
else if ($amount == 400) $result = 450;
|
||||
else if ($amount == 1000) $result = 1140;
|
||||
|
||||
logError("3.2", "Bonus calculation result", [
|
||||
"input" => $amount,
|
||||
"output" => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
include "../../../jwtconnect.php";
|
||||
|
||||
// ------------------------------
|
||||
// قراءة HMAC من الهيدر أو من الـ query
|
||||
// ------------------------------
|
||||
$received_hmac = $_SERVER['HTTP_HMAC'] ?? ($_GET['hmac'] ?? '');
|
||||
$received_hmac = trim($received_hmac);
|
||||
|
||||
// ------------------------------
|
||||
// قراءة البيانات القادمة من Paymob
|
||||
// ------------------------------
|
||||
$raw_body = file_get_contents("php://input");
|
||||
$data = json_decode($raw_body, true);
|
||||
|
||||
// ------------------------------
|
||||
// المفتاح السري
|
||||
// ------------------------------
|
||||
$secret_key = getenv('hmacPaymob');
|
||||
|
||||
// ------------------------------
|
||||
// دالة لتحويل القيم إلى النصوص
|
||||
// ------------------------------
|
||||
function normalize($value) {
|
||||
if ($value === true) return 'true';
|
||||
if ($value === false) return 'false';
|
||||
if (is_null($value)) return '';
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// التحقق من صحة HMAC
|
||||
// ------------------------------
|
||||
function isValidHmac($data, $secret_key, $received_hmac) {
|
||||
if (!isset($data['obj'])) return false;
|
||||
|
||||
$obj = $data['obj'];
|
||||
|
||||
// دمج جميع الحقول بشكل متسلسل
|
||||
$fields = [
|
||||
normalize($obj['amount_cents'] ?? ''),
|
||||
normalize($obj['created_at'] ?? ''),
|
||||
normalize($obj['currency'] ?? ''),
|
||||
normalize($obj['error_occured'] ?? false),
|
||||
normalize($obj['has_parent_transaction'] ?? false),
|
||||
normalize($obj['id'] ?? ''),
|
||||
normalize($obj['integration_id'] ?? ''),
|
||||
normalize($obj['is_3d_secure'] ?? false),
|
||||
normalize($obj['is_auth'] ?? false),
|
||||
normalize($obj['is_capture'] ?? false),
|
||||
normalize($obj['is_refunded'] ?? false),
|
||||
normalize($obj['is_standalone_payment'] ?? false),
|
||||
normalize($obj['is_voided'] ?? false),
|
||||
normalize($obj['order']['id'] ?? ''),
|
||||
normalize($obj['owner'] ?? ''),
|
||||
normalize($obj['pending'] ?? false),
|
||||
normalize($obj['source_data']['pan'] ?? ''),
|
||||
normalize($obj['source_data']['sub_type'] ?? ''),
|
||||
normalize($obj['source_data']['type'] ?? ''),
|
||||
normalize($obj['success'] ?? false)
|
||||
];
|
||||
|
||||
// دمج الحقول في رسالة واحدة
|
||||
$message = implode('', $fields);
|
||||
|
||||
// حساب HMAC باستخدام المفتاح السري
|
||||
$calculated_hmac = hash_hmac('sha512', $message, $secret_key);
|
||||
|
||||
// طباعة الرسائل لأغراض التصحيح
|
||||
// error_log("🔐 Message used for HMAC: " . $message);
|
||||
// error_log("🔐 Calculated HMAC: " . $calculated_hmac);
|
||||
// error_log("📩 Received HMAC: " . $received_hmac);
|
||||
// error_log("Calculated HMAC length: " . strlen($calculated_hmac));
|
||||
// error_log("Received HMAC length: " . strlen($received_hmac));
|
||||
|
||||
// التحقق من تطابق HMAC
|
||||
if (hash_equals($calculated_hmac, $received_hmac)) {
|
||||
error_log("✅ Valid HMAC signature verified.");
|
||||
return $calculated_hmac;
|
||||
} else {
|
||||
http_response_code(401);
|
||||
echo json_encode(["error" => "Unauthorized – Invalid HMAC"]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
isValidHmac($data, $secret_key, $received_hmac);
|
||||
// ------------------------------
|
||||
// إذا كانت HMAC صحيحة، نتابع العملية
|
||||
// ------------------------------
|
||||
if ($data && isset($data['obj'])) {
|
||||
$transaction = $data['obj'];
|
||||
|
||||
$payment_id = $transaction['id'] ?? null;
|
||||
$amount = $transaction['amount_cents'] ?? 0;
|
||||
$status = $transaction['success'] ?? false;
|
||||
$is_voided = $transaction['is_voided'] ?? false;
|
||||
$is_refunded = $transaction['is_refunded'] ?? false;
|
||||
$order_id = $transaction['order']['id'] ?? null;
|
||||
$merchant_order_id = $transaction['order']['merchant_order_id'] ?? null;
|
||||
$payment_method = $transaction['source_data']['type'] ?? 'unknown';
|
||||
$card_last4 = $transaction['source_data']['pan'] ?? '****';
|
||||
$transaction_type = $transaction['data']['migs_transaction']['type'] ?? 'UNKNOWN';
|
||||
$created_at = $transaction['created_at'] ?? date("Y-m-d H:i:s");
|
||||
$user_id = $transaction['order']['shipping_data']['phone_number'];
|
||||
|
||||
// التحقق من حالة الدفع
|
||||
if (!$status) {
|
||||
error_log("❌ Invalid payment status: " . $status);
|
||||
echo json_encode(["error" => "Invalid payment status"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// إضافة البيانات إلى قاعدة البيانات
|
||||
$query = "INSERT INTO paymentsLog (`payment_id`, `user_id`, `amount`, `status`)
|
||||
VALUES (:payment_id, :user_id, :amount, :status)";
|
||||
|
||||
$stmt = $con->prepare($query);
|
||||
$stmt->bindParam(':payment_id', $payment_id);
|
||||
$stmt->bindParam(':user_id', $user_id);
|
||||
$stmt->bindParam(':amount', $amount);
|
||||
$stmt->bindParam(':status', $status);
|
||||
|
||||
try {
|
||||
$stmt->execute();
|
||||
if ($stmt->rowCount() > 0) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => true, "message" => "Payment data saved successfully"]);
|
||||
} else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["success" => false, "message" => "Payment data already up to date."]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Failed to execute the query: " . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
include "../../connect.php";
|
||||
///ride/payment/add.php
|
||||
$amount = filterRequest("amount");
|
||||
$payment_method = filterRequest("payment_method");
|
||||
$passengerID = filterRequest("passengerID");
|
||||
$rideId = filterRequest("rideId");
|
||||
$driverID = filterRequest("driverID");
|
||||
$token = filterRequest("token");
|
||||
|
||||
try {
|
||||
$con->beginTransaction();
|
||||
|
||||
// ✅ تحقق من التوكن مع قفل السجل (FOR UPDATE) لمنع ثغرة السباق (Race Condition)
|
||||
$stmt = $con->prepare("SELECT * FROM payment_tokens WHERE token = :token AND isUsed = FALSE FOR UPDATE");
|
||||
$stmt->execute([ ':token' => $token ]);
|
||||
$tokenData = $stmt->fetch();
|
||||
|
||||
if ($tokenData) {
|
||||
// ✅ إدخال الدفع بمفتاح قصير وخفيف
|
||||
$sql = "INSERT INTO payments (id, amount, payment_method, passengerID, rideId, driverID)
|
||||
VALUES (UUID_SHORT(), :amount, :payment_method, :passengerID, :rideId, :driverID)";
|
||||
$stmtInsert = $con->prepare($sql);
|
||||
$stmtInsert->execute([
|
||||
':amount' => $amount,
|
||||
':payment_method' => $payment_method,
|
||||
':passengerID' => $passengerID,
|
||||
':rideId' => $rideId,
|
||||
':driverID' => $driverID
|
||||
]);
|
||||
|
||||
if ($stmtInsert->rowCount() > 0) {
|
||||
// ✅ تحديث حالة التوكن
|
||||
$stmtUpdate = $con->prepare("UPDATE payment_tokens SET isUsed = TRUE WHERE id = :tokenID");
|
||||
$stmtUpdate->execute([ ':tokenID' => $tokenData['id'] ]);
|
||||
|
||||
$con->commit();
|
||||
printSuccess("Payment record created successfully");
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Failed to save record");
|
||||
}
|
||||
} else {
|
||||
$con->rollBack();
|
||||
printFailure("Invalid or already used token");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($con->inTransaction()) {
|
||||
$con->rollBack();
|
||||
}
|
||||
error_log("[payment/add] " . $e->getMessage());
|
||||
printFailure("An error occurred");
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
|
||||
include "../../connect.php";
|
||||
$driverID = filterRequest("driverID");
|
||||
|
||||
// الخطوة 1: جلب كل سجلات الدفع لليوم الحالي
|
||||
$sql_records = "SELECT
|
||||
id,
|
||||
amount,
|
||||
payment_method,
|
||||
isGiven,
|
||||
passengerID,
|
||||
rideId,
|
||||
created_at
|
||||
FROM
|
||||
payments
|
||||
WHERE
|
||||
driverID = ?
|
||||
AND DATE(created_at) = CURDATE()
|
||||
";
|
||||
|
||||
$stmt_records = $con->prepare($sql_records);
|
||||
$stmt_records->execute([$driverID]);
|
||||
$records = $stmt_records->fetchAll(PDO::FETCH_ASSOC);
|
||||
$count = $stmt_records->rowCount();
|
||||
|
||||
if ($count > 0) {
|
||||
// الخطوة 2: حساب المجموع اليومي في استعلام منفصل وآمن
|
||||
$sql_sum = "SELECT
|
||||
COALESCE(SUM(amount), 0) AS todayAmount
|
||||
FROM
|
||||
payments
|
||||
WHERE
|
||||
driverID = ?
|
||||
AND DATE(created_at) = CURDATE()
|
||||
-- AND isGiven !='waiting'";
|
||||
|
||||
$stmt_sum = $con->prepare($sql_sum);
|
||||
$stmt_sum->execute([$driverID]);
|
||||
$total_row = $stmt_sum->fetch(PDO::FETCH_ASSOC);
|
||||
$todayAmount = $total_row['todayAmount'];
|
||||
|
||||
// الخطوة 3: إضافة المجموع الكلي لكل سجل في القائمة
|
||||
$response_data = [];
|
||||
foreach ($records as $record) {
|
||||
$record['todayAmount'] = $todayAmount; // أضف المجموع هنا
|
||||
$response_data[] = $record;
|
||||
}
|
||||
|
||||
// إرسال البيانات بالهيكلية التي يتوقعها التطبيق
|
||||
printSuccess( $response_data);
|
||||
|
||||
} else {
|
||||
// في حالة عدم وجود أي دفعات اليوم
|
||||
printFailure($message = "No wallet record found");
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user