Compare commits
8 Commits
671b90a954
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c6e110cd0 | ||
|
|
27e9d89af3 | ||
|
|
d0211ecb86 | ||
|
|
cc088decfd | ||
|
|
6d65f4d09f | ||
|
|
d20e041009 | ||
|
|
fadb373d42 | ||
|
|
fca292f2a4 |
79
app/Http/Controllers/Api/PaymentTokenController.php
Normal file
79
app/Http/Controllers/Api/PaymentTokenController.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Firebase\JWT\JWT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PaymentTokenController — توليد رموز الدفع المتوافقة مع سيرفر المحفظة V1
|
||||||
|
*
|
||||||
|
* ⚠️ مهم جداً:
|
||||||
|
* سيرفر المحفظة (walletintaleq.intaleq.xyz) يعمل بكود V1 ويتحقق من التوكن باستخدام:
|
||||||
|
* - المفتاح: /home/intaleq-api/.secret_key (وليس PAYMENT_INTERNAL_KEY_PATH)
|
||||||
|
* - الـ Issuer: 'Tripz' أو 'Tripz-Wallet'
|
||||||
|
* - الـ Claim: user_id (وليس sub)
|
||||||
|
*
|
||||||
|
* لذلك يجب أن يكون التوكن المُولّد هنا متوافقاً تماماً مع V1.
|
||||||
|
*/
|
||||||
|
class PaymentTokenController extends Controller
|
||||||
|
{
|
||||||
|
// 1. مسار الراكب
|
||||||
|
public function generatePassengerToken(Request $request)
|
||||||
|
{
|
||||||
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
|
return $this->buildToken($userId, 'android/ios_passenger', $request->input('fingerPrint'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. مسار السائق
|
||||||
|
public function generateDriverToken(Request $request)
|
||||||
|
{
|
||||||
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
|
return $this->buildToken($userId, 'android/ios_driver', $request->input('fingerPrint'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. مسار المدير
|
||||||
|
public function generateAdminToken(Request $request)
|
||||||
|
{
|
||||||
|
$userId = $request->attributes->get('_jwt_user_id');
|
||||||
|
return $this->buildToken($userId, 'web_admin', 'admin_secure_context');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. دالة البناء المركزية — متوافقة مع V1 authenticateJWT()
|
||||||
|
private function buildToken($userId, $audience, $fingerprint)
|
||||||
|
{
|
||||||
|
// ⚠️ مهم جداً لموافاة V1:
|
||||||
|
// سيرفر المحفظة (walletintaleq.intaleq.xyz) يستخدم V1authenticateJWT()
|
||||||
|
// التي تقرأ المفتاح من: /home/intaleq-api/.secret_key
|
||||||
|
// لذلك يجب استخدام jwt_secret هنا وليس wallet_jwt_secret.
|
||||||
|
$secret = config('intaleq.jwt_secret');
|
||||||
|
|
||||||
|
if (empty($secret)) {
|
||||||
|
return response()->json(['status' => 'error', 'message' => 'Security Key Missing (JWT)'], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// بناء التوكن بنفس الهيكل المطلوب من V1
|
||||||
|
$payload = [
|
||||||
|
'user_id' => $userId, // V1 يستخرج: $decoded->user_id
|
||||||
|
'iss' => 'Tripz-Wallet', // V1 يتحقق: $decoded->iss === 'Tripz-Wallet'
|
||||||
|
'aud' => $audience,
|
||||||
|
'iat' => time(),
|
||||||
|
'exp' => time() + 120, // زيادة الوقت قليلاً لـ 120 ثانية
|
||||||
|
'fingerPrint' => hash('sha256', ($fingerprint ?? '') . config('intaleq.fp_pepper', '')),
|
||||||
|
'jti' => bin2hex(random_bytes(16)),
|
||||||
|
];
|
||||||
|
|
||||||
|
$token = JWT::encode($payload, $secret, 'HS256');
|
||||||
|
|
||||||
|
// HMAC: يستخدم SECRET_KEY_HMAC (متوفر في .env)
|
||||||
|
$hmacSecret = config('intaleq.wallet_hmac_secret');
|
||||||
|
$hmac = hash_hmac('sha256', $userId, $hmacSecret);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'token' => $token,
|
||||||
|
'hmac' => $hmac
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -323,25 +323,28 @@ class AuthController extends Controller
|
|||||||
'created_at' => now(),
|
'created_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 11. Insert Documents
|
// 11. Insert Documents (Wait, the user said registration response MUST return jwt, api_secret, and hmac immediately to allow subsequent protected file uploads)
|
||||||
|
// So we don't need to insert documents here if they are uploaded later. But keeping the existing code won't hurt, just the response changes.
|
||||||
$docKeys = [
|
$docKeys = [
|
||||||
'driver_license_front', 'driver_license_back',
|
'driver_license_front', 'driver_license_back',
|
||||||
'car_license_front', 'car_license_back'
|
'car_license_front', 'car_license_back'
|
||||||
];
|
];
|
||||||
$docUrls = [];
|
$docUrls = [];
|
||||||
foreach ($docKeys as $k) {
|
foreach ($docKeys as $k) {
|
||||||
$url = $request->input($k);
|
if ($request->has($k)) {
|
||||||
$docUrls[$k] = $url;
|
$url = $request->input($k);
|
||||||
$name = basename(parse_url($url, PHP_URL_PATH) ?? '');
|
$docUrls[$k] = $url;
|
||||||
if ($name === '') { $name = $k . '_' . time() . '.jpg'; }
|
$name = basename(parse_url($url, PHP_URL_PATH) ?? '');
|
||||||
|
if ($name === '') { $name = $k . '_' . time() . '.jpg'; }
|
||||||
|
|
||||||
DB::connection('primary')->table('driver_documents')->insert([
|
DB::connection('primary')->table('driver_documents')->insert([
|
||||||
'driverID' => $data['id'],
|
'driverID' => $data['id'],
|
||||||
'doc_type' => $k,
|
'doc_type' => $k,
|
||||||
'image_name' => $name,
|
'image_name' => $name,
|
||||||
'link' => $url,
|
'link' => $url,
|
||||||
'upload_date' => now(),
|
'upload_date' => now(),
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::connection('primary')->commit();
|
DB::connection('primary')->commit();
|
||||||
@@ -360,11 +363,29 @@ class AuthController extends Controller
|
|||||||
\Illuminate\Support\Facades\Log::error("FCM Admin notification failed: " . $e->getMessage());
|
\Illuminate\Support\Facades\Log::error("FCM Admin notification failed: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate JWT and API Keys
|
||||||
|
$fingerprint = $request->input('fingerprint', 'unknown');
|
||||||
|
$jwt = $this->createJwt($data['id'], 'driver', $fingerprint, 14400);
|
||||||
|
|
||||||
|
$apiKey = bin2hex(random_bytes(16));
|
||||||
|
$apiSecret = bin2hex(random_bytes(32));
|
||||||
|
DB::connection('primary')->table('driver')->where('id', $data['id'])->update([
|
||||||
|
'api_key' => $apiKey,
|
||||||
|
'api_secret' => $apiSecret
|
||||||
|
]);
|
||||||
|
|
||||||
|
// To be compatible with frontend, generate Wallet HMAC
|
||||||
|
$hmac = hash_hmac('sha256', $jwt, config('intaleq.secret_key_hmac', ''));
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'driverID' => $data['id'],
|
'driverID' => $data['id'],
|
||||||
'carRegID' => $carRegId,
|
'carRegID' => $carRegId,
|
||||||
'documents' => $docUrls
|
'documents' => $docUrls,
|
||||||
|
'jwt' => $jwt,
|
||||||
|
'api_key' => $apiKey,
|
||||||
|
'api_secret' => $apiSecret,
|
||||||
|
'hmac' => $hmac
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -617,7 +638,7 @@ class AuthController extends Controller
|
|||||||
$fingerPrint = $request->input('fingerPrint');
|
$fingerPrint = $request->input('fingerPrint');
|
||||||
|
|
||||||
// Check if audience is allowed
|
// Check if audience is allowed
|
||||||
$allowedAudiences = [config('intaleq.allowed_wallet_1'), config('intaleq.allowed_wallet_2')];
|
$allowedAudiences = config('intaleq.wallet_allowed_audiences', []);
|
||||||
if (!in_array($audience, $allowedAudiences)) {
|
if (!in_array($audience, $allowedAudiences)) {
|
||||||
return $this->failure('Invalid audience');
|
return $this->failure('Invalid audience');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,24 @@ class MiscController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** GET /v2/misc/package-info */
|
/** GET /v2/misc/package-info */
|
||||||
public function packageInfo(): JsonResponse
|
public function packageInfo(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$info = DB::connection('primary')->table('packageInfo')->orderBy('id', 'desc')->first();
|
$platform = $request->input('platform', 'android');
|
||||||
|
$appName = $request->input('appName');
|
||||||
|
|
||||||
|
$query = DB::connection('primary')->table('packageInfo')
|
||||||
|
->where('platform', $platform);
|
||||||
|
|
||||||
|
if ($appName) {
|
||||||
|
$query->where('appName', $appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = $query->orderBy('id', 'desc')->first();
|
||||||
|
|
||||||
if (!$info) {
|
if (!$info) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'failure',
|
'status' => 'failure',
|
||||||
'message' => 'No package info found'
|
'message' => 'No package info found for platform: ' . $platform
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
127
app/Http/Controllers/OverlayController.php
Normal file
127
app/Http/Controllers/OverlayController.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OverlayController — إدارة بيانات الرحلة المقبولة من الخلفية
|
||||||
|
*
|
||||||
|
* الغرض:
|
||||||
|
* عندما يقبل السائق رحلة وهو في الخلفية (Background)، يتم تخزين بيانات الرحلة
|
||||||
|
* في جدول write_argument_after_applied_from_background حتى يتمكن التطبيق من
|
||||||
|
* استرجاعها عند العودة للواجهة (Foreground).
|
||||||
|
*
|
||||||
|
* يعادل: intaleq_v1/ride/overLay/getArgumentAfterAppliedFromBackground.php
|
||||||
|
* intaleq_v1/ride/overLay/add.php
|
||||||
|
*/
|
||||||
|
class OverlayController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* GET /v2/overlay/background-args
|
||||||
|
* جلب بيانات الرحلة المقبولة من الخلفية (آخر دقيقتين فقط)
|
||||||
|
*/
|
||||||
|
public function getBackgroundArgs(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$driverId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
|
if (empty($driverId)) {
|
||||||
|
return response()->json(['status' => 'failure', 'message' => 'Missing driver ID']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = DB::connection('primary')
|
||||||
|
->table('write_argument_after_applied_from_background')
|
||||||
|
->where('driver_id', $driverId)
|
||||||
|
->whereRaw('TIMESTAMPDIFF(MINUTE, time_of_order, NOW()) <= 2')
|
||||||
|
->orderBy('time_of_order', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($row) {
|
||||||
|
return response()->json(['status' => 'success', 'message' => (array) $row]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'failure', 'message' => 'No data found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /v2/overlay/background-args
|
||||||
|
* تخزين بيانات الرحلة المقبولة من الخلفية
|
||||||
|
*/
|
||||||
|
public function storeBackgroundArgs(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$driverId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
|
$rideId = $request->input('rideId');
|
||||||
|
$passengerLocation = $request->input('passengerLocation');
|
||||||
|
$passengerDestination = $request->input('passengerDestination');
|
||||||
|
|
||||||
|
if (empty($rideId) || empty($driverId) || empty($passengerLocation) || empty($passengerDestination)) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Missing required fields (rideId, driver_id, or locations)'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::connection('primary')
|
||||||
|
->table('write_argument_after_applied_from_background')
|
||||||
|
->insert([
|
||||||
|
'ride_id' => $rideId,
|
||||||
|
'driver_id' => $driverId,
|
||||||
|
'passenger_id' => $request->input('passengerId'),
|
||||||
|
'passenger_location' => $passengerLocation,
|
||||||
|
'passenger_destination' => $passengerDestination,
|
||||||
|
'duration' => (int) $request->input('Duration', 0),
|
||||||
|
'duration_to_passenger' => (int) $request->input('DurationToPassenger', 0),
|
||||||
|
'duration_of_ride' => (int) $request->input('durationOfRideValue', 0),
|
||||||
|
'distance' => (float) $request->input('Distance', 0),
|
||||||
|
'total_cost' => (float) $request->input('totalCost', 0),
|
||||||
|
'payment_amount' => (float) $request->input('paymentAmount', 0),
|
||||||
|
'payment_method' => $request->input('paymentMethod'),
|
||||||
|
'wallet_checked' => $request->input('WalletChecked') === 'true' ? 1 : 0,
|
||||||
|
'has_steps' => !empty($request->input('isHaveSteps')) ? 1 : 0,
|
||||||
|
'step0' => $request->input('step0'),
|
||||||
|
'step1' => $request->input('step1'),
|
||||||
|
'step2' => $request->input('step2'),
|
||||||
|
'step3' => $request->input('step3'),
|
||||||
|
'step4' => $request->input('step4'),
|
||||||
|
'passenger_wallet_burc' => (float) $request->input('passengerWalletBurc', 0),
|
||||||
|
'token_passenger' => $request->input('tokenPassenger'),
|
||||||
|
'name' => $request->input('name'),
|
||||||
|
'phone' => $request->input('phone'),
|
||||||
|
'email' => $request->input('email'),
|
||||||
|
'start_name_location' => $request->input('startNameLocation'),
|
||||||
|
'end_name_location' => $request->input('endNameLocation'),
|
||||||
|
'car_type' => $request->input('carType'),
|
||||||
|
'kazan' => (float) $request->input('kazan', 0),
|
||||||
|
'direction_url' => $request->input('direction'),
|
||||||
|
'time_of_order' => $request->input('timeOfOrder', now()),
|
||||||
|
'total_passenger' => (int) $request->input('totalPassenger', 0),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success', 'message' => 'Background args saved']);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('OverlayController Error: ' . $e->getMessage());
|
||||||
|
return response()->json(['status' => 'failure', 'message' => 'Database error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /v2/overlay/background-args
|
||||||
|
* حذف بيانات الرحلة المقبولة بعد استلامها
|
||||||
|
*/
|
||||||
|
public function deleteBackgroundArgs(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$driverId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
|
DB::connection('primary')
|
||||||
|
->table('write_argument_after_applied_from_background')
|
||||||
|
->where('driver_id', $driverId)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success', 'message' => 'Background args deleted']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,23 +112,37 @@ class RatingController extends Controller
|
|||||||
if (!$id) {
|
if (!$id) {
|
||||||
return response()->json(['status' => 'failure', 'message' => 'Driver ID required'], 400);
|
return response()->json(['status' => 'failure', 'message' => 'Driver ID required'], 400);
|
||||||
}
|
}
|
||||||
$ratings = DB::connection('primary')->table('ratingDriver')
|
|
||||||
->where('driver_id', $id)
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->limit(50)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
$avg = DB::connection('primary')->table('ratingDriver')
|
$summaryOnly = $request->input('summary_only', true);
|
||||||
->where('driver_id', $id)->avg('rating');
|
|
||||||
|
|
||||||
return response()->json([
|
// Cache rating summary for 1 hour
|
||||||
'status' => 'success',
|
$cacheKey = "driver_rating_summary:{$id}";
|
||||||
'message' => [
|
$summary = \Illuminate\Support\Facades\Cache::remember($cacheKey, 3600, function () use ($id) {
|
||||||
|
$avg = DB::connection('primary')->table('ratingDriver')
|
||||||
|
->where('driver_id', $id)->avg('rating');
|
||||||
|
$count = DB::connection('primary')->table('ratingDriver')
|
||||||
|
->where('driver_id', $id)->count();
|
||||||
|
|
||||||
|
return [
|
||||||
'average' => round($avg ?? 5.0, 2),
|
'average' => round($avg ?? 5.0, 2),
|
||||||
'count' => $ratings->count(),
|
'count' => $count,
|
||||||
'ratings' => $ratings,
|
];
|
||||||
],
|
});
|
||||||
]);
|
|
||||||
|
$response = [
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $summary,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!$summaryOnly) {
|
||||||
|
$response['message']['ratings'] = DB::connection('primary')->table('ratingDriver')
|
||||||
|
->where('driver_id', $id)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->limit(50)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GET /v2/ratings/passenger/{id} */
|
/** GET /v2/ratings/passenger/{id} */
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ class RideController extends Controller
|
|||||||
|
|
||||||
public function store(Request $request): JsonResponse
|
public function store(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
// Alias support for legacy app versions
|
||||||
|
if ($request->has('carType') && !$request->has('car_type')) {
|
||||||
|
$request->merge(['car_type' => $request->input('carType')]);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Validation (Adding new required fields for the 33-item array)
|
// 1. Validation (Adding new required fields for the 33-item array)
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'start_location' => 'required|string',
|
'start_location' => 'required|string',
|
||||||
@@ -118,6 +123,29 @@ class RideController extends Controller
|
|||||||
$priceForDriver = (float) $request->input('price_for_driver', $price);
|
$priceForDriver = (float) $request->input('price_for_driver', $price);
|
||||||
$kazan = $price - $priceForDriver;
|
$kazan = $price - $priceForDriver;
|
||||||
|
|
||||||
|
// Sync with waitingRides table (Marketplace visibility)
|
||||||
|
DB::connection('primary')->table('waitingRides')->insert([
|
||||||
|
'id' => (string)$insertedId,
|
||||||
|
'start_location' => $request->input('start_name', 'Pickup point'),
|
||||||
|
'end_location' => $request->input('end_name', 'Destination'),
|
||||||
|
'date' => $rideData['date'],
|
||||||
|
'time' => $rideData['time'],
|
||||||
|
'price' => $rideData['price'],
|
||||||
|
'passenger_id' => $passengerId,
|
||||||
|
'status' => 'waiting',
|
||||||
|
'carType' => $rideData['carType'],
|
||||||
|
'passengerRate' => $request->input('passenger_rating', '5.0'),
|
||||||
|
'distance' => $rideData['distance'],
|
||||||
|
'duration' => $request->input('duration_text', '0'),
|
||||||
|
'start_lat' => $startLat,
|
||||||
|
'start_lng' => $startLng,
|
||||||
|
'end_lat' => $endLat,
|
||||||
|
'end_lng' => $endLng,
|
||||||
|
'payment_method' => $request->input('is_wallet', '0') == '1' ? 'wallet' : 'cash',
|
||||||
|
'passenger_wallet' => $request->input('passenger_wallet', '0'),
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
$payloadTemplate = [];
|
$payloadTemplate = [];
|
||||||
$payloadTemplate[0] = (string)$startLat;
|
$payloadTemplate[0] = (string)$startLat;
|
||||||
$payloadTemplate[1] = (string)$startLng;
|
$payloadTemplate[1] = (string)$startLng;
|
||||||
@@ -636,4 +664,73 @@ class RideController extends Controller
|
|||||||
|
|
||||||
return response()->json(['status' => 'success', 'message' => $rides]);
|
return response()->json(['status' => 'success', 'message' => $rides]);
|
||||||
}
|
}
|
||||||
|
public function availableRides(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$lat = (float) $request->input('lat');
|
||||||
|
$lng = (float) $request->input('lng');
|
||||||
|
$radius = (float) $request->input('radius', 50); // km
|
||||||
|
|
||||||
|
$driverId = $request->attributes->get('_jwt_user_id');
|
||||||
|
|
||||||
|
// Get driver car type for hierarchical matching
|
||||||
|
$driverCarType = DB::connection('primary')->table('driver as d')
|
||||||
|
->leftJoin('CarRegistration as c', 'c.driverID', '=', 'd.id')
|
||||||
|
->where('d.id', $driverId)
|
||||||
|
->value('c.make') ?? 'Speed';
|
||||||
|
|
||||||
|
$rides = DB::connection('primary')->table('waitingRides as wr')
|
||||||
|
->select([
|
||||||
|
'wr.id', 'wr.start_location as startName', 'wr.end_location as endName',
|
||||||
|
'wr.date', 'wr.time', 'wr.price', 'wr.passenger_id', 'wr.status', 'wr.carType',
|
||||||
|
'wr.passengerRate', 'wr.created_at', 'wr.price_for_passenger',
|
||||||
|
'wr.distance', 'wr.duration', 'wr.start_lat', 'wr.start_lng',
|
||||||
|
'wr.end_lat', 'wr.end_lng', 'wr.payment_method', 'wr.passenger_wallet',
|
||||||
|
'p.email', 'p.first_name', 'p.phone', 'p.id as passengerId', 't.token as passengerToken',
|
||||||
|
DB::raw("( 6371 * acos( cos( radians($lat) ) * cos( radians( wr.start_lat ) ) * cos( radians( wr.start_lng ) - radians($lng) ) + sin( radians($lat) ) * sin( radians( wr.start_lat ) ) ) ) AS driver_distance_km")
|
||||||
|
])
|
||||||
|
->join('passengers as p', 'p.id', '=', 'wr.passenger_id')
|
||||||
|
->leftJoin('tokens as t', 't.passengerID', '=', 'wr.passenger_id')
|
||||||
|
->whereIn('wr.status', ['wait', 'waiting'])
|
||||||
|
->where('wr.created_at', '>=', now()->subHours(24))
|
||||||
|
->having('driver_distance_km', '<=', $radius)
|
||||||
|
->orderBy('driver_distance_km')
|
||||||
|
->get()
|
||||||
|
->filter(function($ride) use ($driverCarType) {
|
||||||
|
return $this->isCarTypeMatch($driverCarType, $ride->carType);
|
||||||
|
})
|
||||||
|
->map(function($ride) {
|
||||||
|
$ride->first_name = !empty($ride->first_name) ? $this->encryption->decrypt($ride->first_name) : 'Passenger';
|
||||||
|
$ride->phone = !empty($ride->phone) ? $this->encryption->decrypt($ride->phone) : '';
|
||||||
|
$ride->email = !empty($ride->email) ? $this->encryption->decrypt($ride->email) : '';
|
||||||
|
$ride->passengerToken = !empty($ride->passengerToken) ? $this->encryption->decrypt($ride->passengerToken) : '';
|
||||||
|
|
||||||
|
$ride->start_location = $ride->start_lat . ',' . $ride->start_lng;
|
||||||
|
$ride->end_location = (!empty($ride->end_lat))
|
||||||
|
? $ride->end_lat . ',' . $ride->end_lng
|
||||||
|
: $ride->endName;
|
||||||
|
|
||||||
|
$ride->id = (string)$ride->id;
|
||||||
|
$ride->driver_distance_km = number_format((float)$ride->driver_distance_km, 1);
|
||||||
|
|
||||||
|
return $ride;
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => $rides
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCarTypeMatch(string $driverType, ?string $rideType): bool
|
||||||
|
{
|
||||||
|
if (!$rideType) return true;
|
||||||
|
|
||||||
|
return match ($driverType) {
|
||||||
|
'Comfort' => in_array($rideType, ['Speed', 'Comfort', 'Fixed Price']),
|
||||||
|
'Lady' => in_array($rideType, ['Comfort', 'Speed', 'Lady']),
|
||||||
|
'Speed', 'Scooter', 'Awfar Car' => $rideType === $driverType,
|
||||||
|
default => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class UploadController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'image' => 'required|file',
|
'image' => 'required|file',
|
||||||
'doc_type' => 'required|string|in:license,registration,criminal,id_front,id_back',
|
'doc_type' => 'required|string|in:license,registration,criminal,id_front,id_back,driver_license_front,driver_license_back,car_license_front,car_license_back',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $this->handleImageUpload($request, 'driver_documents', 'documents');
|
return $this->handleImageUpload($request, 'driver_documents', 'documents');
|
||||||
|
|||||||
@@ -81,10 +81,16 @@ class HmacAuthMiddleware
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute expected signature
|
// Compute expected signature
|
||||||
// METHOD:PATH:TIMESTAMP:NONCE:BODY
|
|
||||||
$method = strtoupper($request->method());
|
$method = strtoupper($request->method());
|
||||||
$path = $request->path(); // returns path without leading slash (e.g. v2/ride/create)
|
$path = $request->path(); // returns path without leading slash (e.g. v2/ride/create)
|
||||||
$body = $request->getContent();
|
|
||||||
|
if (str_contains(strtolower($request->header('Content-Type', '')), 'multipart/form-data')) {
|
||||||
|
$inputs = $request->except(array_keys($request->allFiles()));
|
||||||
|
ksort($inputs);
|
||||||
|
$body = json_encode($inputs);
|
||||||
|
} else {
|
||||||
|
$body = $request->getContent();
|
||||||
|
}
|
||||||
|
|
||||||
$payload = "$method:$path:$timestamp:$nonce:$body";
|
$payload = "$method:$path:$timestamp:$nonce:$body";
|
||||||
$expected = hash_hmac(self::ALGORITHM, $payload, $user->api_secret);
|
$expected = hash_hmac(self::ALGORITHM, $payload, $user->api_secret);
|
||||||
|
|||||||
@@ -63,8 +63,12 @@ return [
|
|||||||
'wallet_hmac_secret' => env('SECRET_KEY_HMAC'),
|
'wallet_hmac_secret' => env('SECRET_KEY_HMAC'),
|
||||||
'wallet_allowed_audiences' => [
|
'wallet_allowed_audiences' => [
|
||||||
'Tripz-Wallet',
|
'Tripz-Wallet',
|
||||||
|
'Tripz-Walletandroid',
|
||||||
|
'Tripz-Walletios',
|
||||||
'TripzWallet:android',
|
'TripzWallet:android',
|
||||||
'TripzWallet:ios',
|
'TripzWallet:ios',
|
||||||
|
'allowedWallet1android',
|
||||||
|
'allowedWallet1ios',
|
||||||
],
|
],
|
||||||
'wallet_app_password' => env('passwordnewpassenger', ''),
|
'wallet_app_password' => env('passwordnewpassenger', ''),
|
||||||
'fp_pepper' => env('FP_PEPPER', ''),
|
'fp_pepper' => env('FP_PEPPER', ''),
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ use App\Http\Controllers\MiscController;
|
|||||||
use App\Http\Controllers\InviteController;
|
use App\Http\Controllers\InviteController;
|
||||||
use App\Http\Controllers\DriverDocController;
|
use App\Http\Controllers\DriverDocController;
|
||||||
use App\Http\Controllers\SupportController;
|
use App\Http\Controllers\SupportController;
|
||||||
|
use App\Http\Controllers\Api\PaymentTokenController;
|
||||||
|
use App\Http\Controllers\OverlayController;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -48,7 +51,8 @@ Route::prefix('v2/auth')->group(function () {
|
|||||||
// Passenger
|
// Passenger
|
||||||
Route::post('/passenger/login', [AuthController::class, 'passengerLogin']);
|
Route::post('/passenger/login', [AuthController::class, 'passengerLogin']);
|
||||||
Route::post('/passenger/register', [AuthController::class, 'passengerRegister']);
|
Route::post('/passenger/register', [AuthController::class, 'passengerRegister']);
|
||||||
Route::post('/passenger/wallet-login', [AuthController::class, 'passengerWalletLogin']);
|
// to be replaced by dedicated PaymentTokenController for better separation of concerns
|
||||||
|
// Route::post('/passenger/wallet-login', [AuthController::class, 'passengerWalletLogin']);
|
||||||
Route::get('/passenger/login-google', [AuthController::class, 'passengerLoginGoogle']);
|
Route::get('/passenger/login-google', [AuthController::class, 'passengerLoginGoogle']);
|
||||||
|
|
||||||
// Driver
|
// Driver
|
||||||
@@ -63,9 +67,16 @@ Route::prefix('v2/auth')->group(function () {
|
|||||||
// Silent JWT Handshake (Compatibility with V1 background flow)
|
// Silent JWT Handshake (Compatibility with V1 background flow)
|
||||||
Route::post('/passenger/login-jwt', [AuthController::class, 'passengerJwtHandshake']);
|
Route::post('/passenger/login-jwt', [AuthController::class, 'passengerJwtHandshake']);
|
||||||
Route::post('/driver/login-jwt', [AuthController::class, 'driverJwtHandshake']);
|
Route::post('/driver/login-jwt', [AuthController::class, 'driverJwtHandshake']);
|
||||||
Route::post('/driver/wallet-token', [AuthController::class, 'getWalletToken']);
|
// Route::post('/driver/wallet-token', [AuthController::class, 'getWalletToken']);
|
||||||
|
// to be replaced by dedicated PaymentTokenController for better separation of concerns
|
||||||
});
|
});
|
||||||
|
Route::prefix('v2/payment')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
||||||
|
Route::post('/passenger/generate-token', [PaymentTokenController::class, 'generatePassengerToken']);
|
||||||
|
Route::post('/driver/generate-token', [PaymentTokenController::class, 'generateDriverToken']);
|
||||||
|
|
||||||
|
// يفضل إضافة مسار الإدارة داخل مجموعة الإدارة الموجودة مسبقاً أو حمايته بـ middleware إضافي
|
||||||
|
Route::post('/admin/generate-token', [PaymentTokenController::class, 'generateAdminToken'])->middleware('admin');
|
||||||
|
});
|
||||||
// Admin Error Logging (public — accepts error reports from Flutter apps)
|
// Admin Error Logging (public — accepts error reports from Flutter apps)
|
||||||
Route::post('v2/admin/errors', [MiscController::class, 'logClientError']);
|
Route::post('v2/admin/errors', [MiscController::class, 'logClientError']);
|
||||||
|
|
||||||
@@ -92,6 +103,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
|||||||
Route::post('/rides', [RideController::class, 'store']);
|
Route::post('/rides', [RideController::class, 'store']);
|
||||||
Route::get('/rides', [RideController::class, 'index']);
|
Route::get('/rides', [RideController::class, 'index']);
|
||||||
Route::match(['get', 'post'], '/rides/active', [RideController::class, 'active']);
|
Route::match(['get', 'post'], '/rides/active', [RideController::class, 'active']);
|
||||||
|
Route::get('/rides/available', [RideController::class, 'availableRides']);
|
||||||
Route::get('/rides/{id}', [RideController::class, 'show']);
|
Route::get('/rides/{id}', [RideController::class, 'show']);
|
||||||
Route::post('/rides/{id}/accept', [RideController::class, 'accept']);
|
Route::post('/rides/{id}/accept', [RideController::class, 'accept']);
|
||||||
Route::post('/rides/{id}/arrive', [RideController::class, 'arrive']);
|
Route::post('/rides/{id}/arrive', [RideController::class, 'arrive']);
|
||||||
@@ -100,6 +112,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
|||||||
Route::post('/rides/{id}/cancel/passenger', [RideController::class, 'cancelByPassenger']);
|
Route::post('/rides/{id}/cancel/passenger', [RideController::class, 'cancelByPassenger']);
|
||||||
Route::post('/rides/{id}/cancel/driver', [RideController::class, 'cancelByDriver']);
|
Route::post('/rides/{id}/cancel/driver', [RideController::class, 'cancelByDriver']);
|
||||||
Route::post('/rides/{id}/retry', [RideController::class, 'retrySearch']);
|
Route::post('/rides/{id}/retry', [RideController::class, 'retrySearch']);
|
||||||
|
|
||||||
Route::put('/rides/{id}', [RideController::class, 'update']);
|
Route::put('/rides/{id}', [RideController::class, 'update']);
|
||||||
|
|
||||||
// ── Tracking ──
|
// ── Tracking ──
|
||||||
@@ -183,6 +196,11 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () {
|
|||||||
|
|
||||||
// ── Support ──
|
// ── Support ──
|
||||||
Route::post('/support/complaints', [SupportController::class, 'storeComplaint']);
|
Route::post('/support/complaints', [SupportController::class, 'storeComplaint']);
|
||||||
|
|
||||||
|
// ── Overlay / Background Args ──
|
||||||
|
Route::get('/overlay/background-args', [OverlayController::class, 'getBackgroundArgs']);
|
||||||
|
Route::post('/overlay/background-args', [OverlayController::class, 'storeBackgroundArgs']);
|
||||||
|
Route::delete('/overlay/background-args', [OverlayController::class, 'deleteBackgroundArgs']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════
|
// ══════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user