نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
178 lines
6.8 KiB
PHP
Executable File
178 lines
6.8 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* process_ride_payments.php — Payment Processing Server
|
|
*
|
|
* Receives S2S (Server-to-Server) requests from finish_ride_updates.php.
|
|
* Authenticated via X-S2S-Api-Key header matching a shared secret.
|
|
*
|
|
* Flow:
|
|
* 1. Validate X-S2S-Api-Key header
|
|
* 2. BEGIN TRANSACTION
|
|
* 3. Insert payment record
|
|
* 4. Deduct from passenger wallet (if walletChecked)
|
|
* 5. Settle passenger debt (if negative balance)
|
|
* 6. Deduct driver points (8%)
|
|
* 7. COMMIT / ROLLBACK on failure
|
|
*/
|
|
|
|
// Adjust path as needed for your payment server structure
|
|
require_once __DIR__ . '/../../jwtconnect.php';
|
|
|
|
// === Secure S2S Configuration ===
|
|
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
|
|
|
|
// ============================================================
|
|
// 1. API Key Authentication (X-S2S-Api-Key header)
|
|
// ============================================================
|
|
$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;
|
|
}
|
|
|
|
// ============================================================
|
|
// 2. Receive All Required Parameters
|
|
// ============================================================
|
|
$rideId = filterRequest("rideId");
|
|
$driverId = filterRequest("driverId");
|
|
$passengerId = filterRequest("passengerId");
|
|
$paymentAmount = filterRequest("paymentAmount");
|
|
$paymentMethod = filterRequest("paymentMethod");
|
|
$walletChecked = filterRequest("walletChecked"); // 'true' or 'false'
|
|
$authToken = filterRequest("authToken"); // kept for logging/audit, not used for auth
|
|
$is_zero_commission = filterRequest("is_zero_commission"); // 'true' or 'false'
|
|
$kazanPercent = filterRequest("kazanPercent"); // dynamic commission rate
|
|
|
|
// --- Validate required fields ---
|
|
if (empty($rideId) || empty($driverId) || empty($passengerId) ||
|
|
!isset($paymentAmount) || empty($paymentMethod) ||
|
|
!isset($walletChecked)) {
|
|
printFailure("Missing required parameters for payment processing.");
|
|
exit;
|
|
}
|
|
|
|
// ============================================================
|
|
// 3. Atomic Payment Processing
|
|
// ============================================================
|
|
try {
|
|
// --- Begin Transaction ---
|
|
$con->beginTransaction();
|
|
|
|
// 3a. Insert main payment record
|
|
$finalPaymentMethod = ($walletChecked === 'true') ? $paymentMethod . "Ride" : $paymentMethod;
|
|
$stmtPayment = $con->prepare(
|
|
"INSERT INTO payments (id, amount, payment_method, passengerID, rideId, driverID)
|
|
VALUES (UUID_SHORT(), :amount, :payment_method, :passengerID, :rideId, :driverID)"
|
|
);
|
|
$stmtPayment->execute([
|
|
':amount' => $paymentAmount,
|
|
':payment_method' => $finalPaymentMethod,
|
|
':passengerID' => $passengerId,
|
|
':rideId' => $rideId,
|
|
':driverID' => $driverId,
|
|
]);
|
|
|
|
if ($stmtPayment->rowCount() <= 0) {
|
|
throw new Exception("Failed to create payment record.");
|
|
}
|
|
|
|
// 3b. Deduct from passenger wallet (if wallet payment)
|
|
if ($walletChecked === 'true') {
|
|
// Check actual balance with row lock
|
|
$stmtBalance = $con->prepare(
|
|
"SELECT COALESCE(SUM(balance), 0) AS current_balance
|
|
FROM passengerWallet
|
|
WHERE passenger_id = :passenger_id
|
|
FOR UPDATE"
|
|
);
|
|
$stmtBalance->execute([':passenger_id' => $passengerId]);
|
|
$balanceRow = $stmtBalance->fetch(PDO::FETCH_ASSOC);
|
|
$currentBalance = floatval($balanceRow['current_balance']);
|
|
|
|
$deductionAmount = floatval($paymentAmount);
|
|
if ($currentBalance < $deductionAmount) {
|
|
throw new Exception("Insufficient wallet balance. Required: $deductionAmount, Available: $currentBalance");
|
|
}
|
|
|
|
$stmtPassengerWallet = $con->prepare(
|
|
"INSERT INTO `passengerWallet` (`passenger_id`, `balance`)
|
|
VALUES (:passenger_id, :balance)"
|
|
);
|
|
$stmtPassengerWallet->execute([
|
|
':passenger_id' => $passengerId,
|
|
':balance' => (-1) * $deductionAmount,
|
|
]);
|
|
|
|
if ($stmtPassengerWallet->rowCount() <= 0) {
|
|
throw new Exception("Failed to deduct from passenger wallet.");
|
|
}
|
|
|
|
// 3c. Settle existing debt (use actual balance after deduction)
|
|
$stmtBalanceAfter = $con->prepare(
|
|
"SELECT COALESCE(SUM(balance), 0) AS balance_after
|
|
FROM passengerWallet
|
|
WHERE passenger_id = :passenger_id
|
|
FOR UPDATE"
|
|
);
|
|
$stmtBalanceAfter->execute([':passenger_id' => $passengerId]);
|
|
$balanceAfterRow = $stmtBalanceAfter->fetch(PDO::FETCH_ASSOC);
|
|
$balanceAfter = floatval($balanceAfterRow['balance_after']);
|
|
|
|
if ($balanceAfter < 0) {
|
|
$stmtPassengerDebt = $con->prepare(
|
|
"INSERT INTO `passengerWallet` (`passenger_id`, `balance`)
|
|
VALUES (:passenger_id, :balance)"
|
|
);
|
|
$stmtPassengerDebt->execute([
|
|
':passenger_id' => $passengerId,
|
|
':balance' => (-1) * $balanceAfter,
|
|
]);
|
|
|
|
if ($stmtPassengerDebt->rowCount() <= 0) {
|
|
throw new Exception("Failed to settle passenger debt.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3d. Deduct driver points (dynamic commission rate)
|
|
$commissionRate = 0.08; // default fallback 8%
|
|
if (isset($kazanPercent) && is_numeric($kazanPercent)) {
|
|
$commissionRate = floatval($kazanPercent) / 100;
|
|
}
|
|
|
|
$pointsSubtraction = floatval($paymentAmount) * (-$commissionRate);
|
|
if ($is_zero_commission === 'true') {
|
|
$pointsSubtraction = 0; // 🆕 لا نخصم عمولة أبداً لأن السائق لديه تتابع نشط
|
|
}
|
|
|
|
$stmtDriverPoints = $con->prepare(
|
|
"INSERT INTO `driverWallet` (`driverID`, `paymentID`, `amount`, `paymentMethod`)
|
|
VALUES (:driverID, :paymentID, :amount, :paymentMethod)"
|
|
);
|
|
$stmtDriverPoints->execute([
|
|
':driverID' => $driverId,
|
|
':paymentID' => 'rideId' . $rideId,
|
|
':amount' => number_format($pointsSubtraction, 0, '', ''),
|
|
':paymentMethod' => $paymentMethod,
|
|
]);
|
|
|
|
if ($stmtDriverPoints->rowCount() <= 0) {
|
|
throw new Exception("Failed to update driver wallet points.");
|
|
}
|
|
|
|
// --- All operations succeeded → Commit ---
|
|
$con->commit();
|
|
|
|
printSuccess("Payment processed successfully for ride $rideId.");
|
|
|
|
} catch (Exception $e) {
|
|
// --- Any failure → Rollback all changes ---
|
|
if (isset($con) && $con->inTransaction()) {
|
|
$con->rollBack();
|
|
}
|
|
error_log("[process_ride_payments] Transaction FAILED for ride $rideId: " . $e->getMessage());
|
|
printFailure("Transaction failed");
|
|
}
|