"success", "message" => $message, "data" => $data], JSON_UNESCAPED_UNICODE); exit; } } if (!function_exists('printFailure')) { function printFailure($message = "failure", $code = null) { $resp = ["status" => "failure", "message" => $message]; if ($code !== null) $resp["code"] = $code; echo json_encode($resp, JSON_UNESCAPED_UNICODE); exit; } } define("WALLET_LOG", __DIR__ . "/../logs/payment_verification.log"); function wlog($step, $msg, $data = null) { $dir = dirname(WALLET_LOG); if (!is_dir($dir)) { @mkdir($dir, 0755, true); } $line = "[".date('Y-m-d H:i:s')."] STEP {$step}: {$msg}"; if ($data !== null) $line .= " | Data: ".json_encode($data, JSON_UNESCAPED_UNICODE); file_put_contents(WALLET_LOG, $line.PHP_EOL, FILE_APPEND); } // ================== منطق التوكن/المعرف ================== function generateToken(PDO $con, string $driverId, float $amount): ?string { // secretKey يُفترض قادم من connect/env global $secretKey; $data = $driverId.$amount.time().($secretKey ?? 'default_secret'); $hash = hash('sha256', $data); $rand = bin2hex(random_bytes(16)); $token = substr($hash.$rand, 0, 64); $stmt = $con->prepare("INSERT INTO payment_tokens (token, driverID, dateCreated, amount) VALUES (:t, :d, NOW(), :a)"); $stmt->execute([':t'=>$token, ':d'=>$driverId, ':a'=>$amount]); return $stmt->rowCount() ? $token : null; } function generatePaymentID(PDO $con, string $driverId, float $amount, string $method): ?string { $stmt = $con->prepare("INSERT INTO paymentsDriverPoints (amount, payment_method, driverID) VALUES (:a, :m, :d)"); $stmt->execute([':a'=>$amount, ':m'=>$method, ':d'=>$driverId]); return $stmt->rowCount() ? $con->lastInsertId() : null; } // ================== منطق إنهاء الدفع (مضمَّن) ================== function finalizeWalletPaymentInline(PDO $con, string $orderRef): void { // نقفل الصف لمنع السباقات $stmt = $con->prepare("SELECT * FROM paymentsLogSyriaDriver WHERE order_ref = :r LIMIT 1 FOR UPDATE"); $stmt->execute([':r'=>$orderRef]); $payment = $stmt->fetch(PDO::FETCH_ASSOC); if (!$payment) { wlog("FINALIZE", "Payment row not found", ['orderRef'=>$orderRef]); throw new RuntimeException("Payment not found."); } // نتأكد من نجاح مزود الدفع if ((int)$payment['status'] !== 1) { wlog("FINALIZE", "Payment not completed yet", ['orderRef'=>$orderRef, 'status'=>$payment['status']]); throw new RuntimeException("Payment not completed."); } // حارس التكرار: إذا تمّت معالجة المحفظة سابقاً لا نكرر if (!empty($payment['processed_wallet']) && (int)$payment['processed_wallet'] === 1) { wlog("FINALIZE", "Already processed_wallet=1, skip.", ['orderRef'=>$orderRef]); return; // لا نرمي استثناء؛ العملية سبق احتسابها } $driverId = $payment['user_id']; // This is now correctly handled as a string $originalAmount = (float)$payment['amount']; $paymentMethod = $payment['payment_method'] ?: 'ecash'; // البونص $bonusAmount = match ((int)$originalAmount) { 100 => 100.0, 200 => 210.0, 400 => 450.0, 1000 => 1100.0, default => $originalAmount, }; // تنفيذ ككل داخل معاملة $con->beginTransaction(); try { // توليد التوكنات/المعرف $tokenDriver = generateToken($con, $driverId, $bonusAmount); if (!$tokenDriver) throw new RuntimeException("Failed to generate driver token"); $tokenSefer = generateToken($con, $driverId, $originalAmount); if (!$tokenSefer) throw new RuntimeException("Failed to generate sefer token"); $paymentID = generatePaymentID($con, $driverId, $bonusAmount, $paymentMethod); if (!$paymentID) throw new RuntimeException("Failed to create payment ID"); // driverWallet $q = $con->prepare("INSERT INTO driverWallet (driverID, paymentID, amount, paymentMethod) VALUES (:d, :r, :a, :m)"); $q->execute([ ':d'=>$driverId, ':a'=>$bonusAmount, ':m'=>$paymentMethod, ':r'=>$orderRef ]); if ($q->rowCount() === 0) throw new RuntimeException("Insert driverWallet failed"); // وسم التوكن كمستخدم $con->prepare("UPDATE payment_tokens SET isUsed = 1 WHERE token = :t")->execute([':t'=>$tokenDriver]); // seferWallet $q2 = $con->prepare("INSERT INTO seferWallet (driverId, passengerId, amount, paymentMethod, token, createdAt) VALUES (:d, :p, :a, :m, :t, CURRENT_TIMESTAMP)"); $q2->execute([ ':d'=>$driverId, ':p'=>'driver', ':a'=>$originalAmount, ':m'=>$paymentMethod, ':t'=>$tokenSefer ]); if ($q2->rowCount() === 0) throw new RuntimeException("Insert seferWallet failed"); $con->prepare("UPDATE payment_tokens SET isUsed = 1 WHERE token = :t")->execute([':t'=>$tokenSefer]); // تأشير السجل كمنتهٍ للمحفظة $con->prepare("UPDATE paymentsLogSyriaDriver SET processed_wallet = 1, updated_at = NOW() WHERE order_ref = :r")->execute([':r'=>$orderRef]); $con->commit(); wlog("FINALIZE", "Wallets updated successfully", ['orderRef'=>$orderRef, 'driverId'=>$driverId]); } catch (Throwable $e) { $con->rollBack(); wlog("FINALIZE", "Exception: ".$e->getMessage(), ['orderRef'=>$orderRef]); throw $e; } } // ================== مدخل تأكيد الدفع ================== $transactionID = filterRequest('transactionID'); $otp = filterRequest('otp'); error_log("Syriatel Confirm: Request for transaction {$transactionID}"); if (!$transactionID || !$otp) { printFailure("Missing transaction ID or OTP."); } // بيئة $baseUrl = rtrim((string)getenv('SYRIATEL_API_BASE_URL'), '/'); $merchantMSISDN = (string)getenv('SYRIATEL_MERCHANT_MSISDN'); if ($baseUrl === '' || $merchantMSISDN === '') { error_log("Syriatel Confirm: Missing SYRIATEL envs"); printFailure("Server configuration error."); } try { // 1) Auth token $token = function_exists('GetSerialToken') ? GetSerialToken() : getSyriatelToken(); if (!$token) { printFailure("Could not authenticate with the payment provider."); } // 2) Call provider $body = [ "OTP" => $otp, "merchantMSISDN" => $merchantMSISDN, "transactionID" => $transactionID, "token" => $token ]; $url = "{$baseUrl}/ePaymentExternalModule/paymentConfirmation"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_UNICODE), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Accept: application/json", "User-Agent: Mozilla/5.0", ], CURLOPT_CONNECTTIMEOUT => 8, CURLOPT_TIMEOUT => 40, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, ]); $response = curl_exec($ch); $errno = curl_errno($ch); $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); error_log("Syriatel Confirm: HTTP {$httpCode} - ".substr((string)$response, 0, 300)); if ($errno) { printFailure("Payment gateway communication error.", $errno); } if ($httpCode !== 200 || !$response) { printFailure("Payment gateway communication error (HTTP {$httpCode})."); } $responseData = json_decode($response, true); if (!is_array($responseData)) { printFailure("Invalid provider response."); } $ok = (isset($responseData['errorDesc']) && $responseData['errorDesc'] === "Success"); if ($ok) { // نحدّث حالة الدفع، ونُتم المحفظة بشكل آمن داخل نفس الطلب $con->prepare("UPDATE paymentsLogSyriaDriver SET status = 1, updated_at = NOW() WHERE order_ref = :r")->execute([':r'=>$transactionID]); // إنهاء المحفظة (داخلية) $walletOk = true; $walletMsg = null; try { finalizeWalletPaymentInline($con, $transactionID); } catch (Throwable $ex) { $walletOk = false; $walletMsg = $ex->getMessage(); } $payload = [ 'message' => 'Payment confirmed.', 'transactionID' => $transactionID, 'provider' => $responseData ]; if (!$walletOk && $walletMsg) { $payload['walletNotice'] = "Payment successful, but wallet update failed: ".$walletMsg; } printSuccess($payload); } $errorMessage = $responseData['errorDesc'] ?? 'Unknown provider error'; printFailure($errorMessage); } catch (Throwable $e) { error_log("Syriatel Confirm: Exception - ".$e->getMessage()); printFailure("An unexpected server error occurred."); }