driver api setting 1
This commit is contained in:
@@ -146,6 +146,232 @@ class AuthController extends Controller
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function driverRegister(Request $request): JsonResponse
|
||||
{
|
||||
// 1. Validate Required Driver Fields
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'first_name' => 'required|string',
|
||||
'last_name' => 'required|string',
|
||||
// Car Fields
|
||||
'vin' => 'required|string',
|
||||
'car_plate' => 'required|string',
|
||||
'make' => 'required|string',
|
||||
'model' => 'required|string',
|
||||
'year' => 'required|string',
|
||||
'expiration_date' => 'required|string',
|
||||
'color' => 'required|string',
|
||||
'owner' => 'required|string',
|
||||
'color_hex' => 'required|string',
|
||||
'fuel' => 'required|string',
|
||||
// Documents
|
||||
'driver_license_front' => 'required|url',
|
||||
'driver_license_back' => 'required|url',
|
||||
'car_license_front' => 'required|url',
|
||||
'car_license_back' => 'required|url',
|
||||
]);
|
||||
|
||||
$data = $request->all();
|
||||
|
||||
// 2. Format Phone (Syrian Logic)
|
||||
$phone = $data['phone'];
|
||||
$phone = preg_replace('/[ \-\(\)\+]/', '', $phone);
|
||||
$phone = trim($phone);
|
||||
|
||||
if (strpos($phone, '00963') === 0) $phone = substr($phone, 2);
|
||||
elseif (strpos($phone, '0963') === 0) $phone = substr($phone, 1);
|
||||
|
||||
if (strpos($phone, '96309') === 0) $phone = '9639' . substr($phone, 5);
|
||||
elseif (strpos($phone, '9630') === 0) $phone = '9639' . substr($phone, 4);
|
||||
elseif (strpos($phone, '09') === 0) $phone = '963' . substr($phone, 1);
|
||||
elseif (strpos($phone, '9') === 0 && strlen($phone) == 9) $phone = '963' . $phone;
|
||||
elseif (strpos($phone, '0') === 0 && strlen($phone) == 10) $phone = '963' . substr($phone, 1);
|
||||
|
||||
if (strpos($phone, '963') === 0 && strlen($phone) > 3) {
|
||||
if (strpos($phone, '9639') !== 0) {
|
||||
$phone = '9639' . substr($phone, 3);
|
||||
}
|
||||
}
|
||||
$data['phone'] = $phone;
|
||||
|
||||
// 3. Format Birthdate
|
||||
if (!empty($data['birthdate'])) {
|
||||
$data['birthdate'] = trim($data['birthdate']) . '-01-01';
|
||||
} else {
|
||||
$data['birthdate'] = '1970-01-01';
|
||||
}
|
||||
|
||||
// 4. Generate ID & Email
|
||||
if (empty($data['id'])) {
|
||||
$data['id'] = 'DRV' . date('YmdHis') . random_int(1000, 9999);
|
||||
}
|
||||
if (empty($data['email'])) {
|
||||
$data['email'] = $data['phone'] . '@intaleqapp.com';
|
||||
}
|
||||
|
||||
// 5. Encrypt Driver Fields
|
||||
$toEncryptDriver = [
|
||||
'phone', 'email', 'first_name', 'last_name', 'name_arabic', 'gender',
|
||||
'national_number', 'address', 'site', 'fullNameMaritial', 'birthdate'
|
||||
];
|
||||
foreach ($toEncryptDriver as $f) {
|
||||
if (!empty($data[$f]) && $data[$f] !== 'Not specified') {
|
||||
$data[$f] = $this->encryption->encrypt($data[$f]);
|
||||
} else {
|
||||
$data[$f] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Encrypt Car Fields
|
||||
$car = [
|
||||
'vin' => $this->encryption->encrypt($request->input('vin')),
|
||||
'car_plate' => $this->encryption->encrypt($request->input('car_plate')),
|
||||
'make' => $request->input('make'),
|
||||
'model' => $request->input('model'),
|
||||
'year' => $request->input('year'),
|
||||
'expiration_date' => $request->input('expiration_date'),
|
||||
'color' => $request->input('color'),
|
||||
'owner' => $this->encryption->encrypt($request->input('owner')),
|
||||
'color_hex' => $request->input('color_hex'),
|
||||
'fuel' => $request->input('fuel'),
|
||||
'vehicle_category_id' => $request->input('vehicle_category_id', 1),
|
||||
'fuel_type_id' => $request->input('fuel_type_id', 1),
|
||||
];
|
||||
|
||||
// 7. Hash Password (HMAC + password_hash)
|
||||
$pepper = config('intaleq.hmac_secret', env('SECRET_KEY_HMAC'));
|
||||
$baseParts = [
|
||||
$data['id'],
|
||||
$phone, // Original formatted phone
|
||||
];
|
||||
if (!empty($request->input('national_number')) && $request->input('national_number') !== 'Not specified') {
|
||||
$baseParts[] = $request->input('national_number');
|
||||
} elseif (!empty($request->input('birthdate'))) {
|
||||
$year = substr($request->input('birthdate'), 0, 4);
|
||||
if (preg_match('/^\d{4}$/', $year)) {
|
||||
$baseParts[] = $year;
|
||||
}
|
||||
}
|
||||
$baseString = implode('|', $baseParts);
|
||||
$rawSecret = hash_hmac('sha256', $baseString, $pepper, true);
|
||||
$pwdHashed = password_hash($rawSecret, PASSWORD_DEFAULT);
|
||||
|
||||
// 8. Check Duplicates
|
||||
$dup = DB::connection('primary')->table('driver')
|
||||
->where('phone', $data['phone'])
|
||||
->orWhere('email', $data['email'])
|
||||
->exists();
|
||||
|
||||
if ($dup) {
|
||||
return response()->json(['status' => 'Failure', 'data' => 'Phone or email already registered.']);
|
||||
}
|
||||
|
||||
DB::connection('primary')->beginTransaction();
|
||||
try {
|
||||
// 9. Insert Driver
|
||||
DB::connection('primary')->table('driver')->insert([
|
||||
'id' => $data['id'],
|
||||
'phone' => $data['phone'],
|
||||
'email' => $data['email'],
|
||||
'password' => $pwdHashed,
|
||||
'gender' => $data['gender'] ?? 'Male',
|
||||
'license_type' => $request->input('license_type', 'yet'),
|
||||
'national_number' => $data['national_number'],
|
||||
'name_arabic' => $data['name_arabic'],
|
||||
'issue_date' => $request->input('issue_date', '2020-01-01'),
|
||||
'expiry_date' => $request->input('expiry_date', 'yet'),
|
||||
'license_categories' => $request->input('license_categories', 'B'),
|
||||
'address' => $data['address'],
|
||||
'licenseIssueDate' => $request->input('licenseIssueDate', '2020-01-01'),
|
||||
'status' => $request->input('status', 'yet'),
|
||||
'birthdate' => $data['birthdate'],
|
||||
'site' => $data['site'] ?? 'demascus',
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'accountBank' => 'yet',
|
||||
'bankCode' => 'yet',
|
||||
'employmentType' => $request->input('employmentType', 'yet'),
|
||||
'maritalStatus' => $request->input('maritalStatus', 'yet'),
|
||||
'fullNameMaritial' => $data['fullNameMaritial'] ?? 'yet',
|
||||
'expirationDate' => $request->input('expirationDate', 'yet'),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// 10. Insert Car Registration
|
||||
$isDefault = DB::connection('primary')->table('CarRegistration')
|
||||
->where('driverID', $data['id'])->exists() ? 0 : 1;
|
||||
|
||||
$carRegId = DB::connection('primary')->table('CarRegistration')->insertGetId([
|
||||
'driverID' => $data['id'],
|
||||
'vin' => $car['vin'],
|
||||
'car_plate' => $car['car_plate'],
|
||||
'make' => $car['make'],
|
||||
'model' => $car['model'],
|
||||
'year' => $car['year'],
|
||||
'expiration_date' => $car['expiration_date'],
|
||||
'color' => $car['color'],
|
||||
'owner' => $car['owner'],
|
||||
'color_hex' => $car['color_hex'],
|
||||
'fuel' => $car['fuel'],
|
||||
'vehicle_category_id' => $car['vehicle_category_id'],
|
||||
'fuel_type_id' => $car['fuel_type_id'],
|
||||
'isDefault' => $isDefault,
|
||||
'status' => 'yet',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
// 11. Insert Documents
|
||||
$docKeys = [
|
||||
'driver_license_front', 'driver_license_back',
|
||||
'car_license_front', 'car_license_back'
|
||||
];
|
||||
$docUrls = [];
|
||||
foreach ($docKeys as $k) {
|
||||
$url = $request->input($k);
|
||||
$docUrls[$k] = $url;
|
||||
$name = basename(parse_url($url, PHP_URL_PATH) ?? '');
|
||||
if ($name === '') { $name = $k . '_' . time() . '.jpg'; }
|
||||
|
||||
DB::connection('primary')->table('driver_documents')->insert([
|
||||
'driverID' => $data['id'],
|
||||
'doc_type' => $k,
|
||||
'image_name' => $name,
|
||||
'link' => $url,
|
||||
'upload_date' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
DB::connection('primary')->commit();
|
||||
|
||||
// 12. FCM Notification to Admin (Fire and Forget)
|
||||
try {
|
||||
$fcm = resolve(\App\Services\FcmService::class);
|
||||
$driverFullName = $request->input('first_name') . ' ' . $request->input('last_name');
|
||||
$fcm->sendToTopic(
|
||||
'service',
|
||||
'تسجيل سائق جديد',
|
||||
"سائق جديد ($driverFullName) سجل برقم ID: {$data['id']} وهو بانتظار المراجعة والتفعيل.",
|
||||
['category' => 'new_driver_registration']
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error("FCM Admin notification failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'driverID' => $data['id'],
|
||||
'carRegID' => $carRegId,
|
||||
'documents' => $docUrls
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::connection('primary')->rollBack();
|
||||
return response()->json(['status' => 'Failure', 'message' => 'Registration failed: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// DRIVER LOGIN
|
||||
// ══════════════════════════════════════════════
|
||||
@@ -153,28 +379,126 @@ class AuthController extends Controller
|
||||
public function driverLogin(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|string',
|
||||
'email' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fingerprint' => 'required|string',
|
||||
'fcm_token' => 'required|string',
|
||||
]);
|
||||
|
||||
$encryptedPhone = $this->encryption->encrypt($request->input('phone'));
|
||||
$driver = Driver::active()->where('phone', $encryptedPhone)->first();
|
||||
$email = $request->input('email');
|
||||
$searchEmail = (str_contains($email, '+') || str_contains($email, '/')) ? $email : $this->encryption->encrypt($email);
|
||||
|
||||
if (!$driver || (!password_verify($request->input('password'), $driver->password) &&
|
||||
!hash_equals($driver->password, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret'))))) {
|
||||
return $this->failure('Invalid credentials');
|
||||
$row = DB::connection('primary')
|
||||
->table('driver as d')
|
||||
->leftJoin('phone_verification', 'phone_verification.phone_number', '=', 'd.phone')
|
||||
->leftJoin('invites', 'invites.inviterDriverPhone', '=', 'd.phone')
|
||||
->leftJoin('shamCash', 'shamCash.driver_id', '=', 'd.id')
|
||||
->leftJoin('CarRegistration', 'CarRegistration.driverID', '=', 'd.id')
|
||||
->leftJoin('captain_bank', 'captain_bank.captain_id', '=', 'd.id')
|
||||
->select([
|
||||
'd.id', 'd.phone', 'd.email', 'd.gender', 'd.status',
|
||||
'd.first_name', 'd.last_name', 'd.password',
|
||||
'd.name_arabic',
|
||||
'phone_verification.verified as is_verified',
|
||||
'invites.isInstall',
|
||||
'shamCash.is_claimed',
|
||||
'CarRegistration.make', 'CarRegistration.model', 'CarRegistration.year',
|
||||
'captain_bank.bankCode', 'captain_bank.accountBank',
|
||||
])
|
||||
->where('d.email', $searchEmail)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return response()->json(['status' => 'failure', 'message' => 'Invalid credentials']);
|
||||
}
|
||||
|
||||
$jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerprint'), 14400); // 4 Hours
|
||||
$driver = (array) $row;
|
||||
|
||||
return $this->success([
|
||||
'token' => $jwt,
|
||||
'expires_in' => 14400,
|
||||
'user_id' => $driver->id,
|
||||
'api_key' => $driver->api_key,
|
||||
'api_secret' => $driver->api_secret,
|
||||
// Verify password
|
||||
if (!password_verify($request->input('password'), $driver['password'])) {
|
||||
return response()->json(['status' => 'failure', 'message' => 'Invalid credentials']);
|
||||
}
|
||||
|
||||
// Decrypt necessary fields
|
||||
$fieldsToDecrypt = ['email', 'phone', 'first_name', 'last_name', 'gender', 'name_arabic'];
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (!empty($driver[$field])) {
|
||||
$dec = $this->encryption->decrypt($driver[$field]);
|
||||
if ($dec) $driver[$field] = $dec;
|
||||
}
|
||||
}
|
||||
unset($driver['password']); // don't send password
|
||||
|
||||
// Default values for null fields
|
||||
$driver['is_verified'] = $driver['is_verified'] ?? 0;
|
||||
$driver['isInstall'] = $driver['isInstall'] ?? 0;
|
||||
$driver['is_claimed'] = $driver['is_claimed'] ?? 0;
|
||||
$driver['bankCode'] = $driver['bankCode'] ?? '';
|
||||
$driver['accountBank'] = $driver['accountBank'] ?? '';
|
||||
$driver['make'] = $driver['make'] ?? '';
|
||||
$driver['model'] = $driver['model'] ?? '';
|
||||
$driver['year'] = $driver['year'] ?? '';
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'data' => [$driver]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function driverLoginGoogle(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['id' => 'required|string']);
|
||||
$id = $request->input('id');
|
||||
|
||||
$row = DB::connection('primary')
|
||||
->table('driver as d')
|
||||
->leftJoin('phone_verification', 'phone_verification.phone_number', '=', 'd.phone')
|
||||
->leftJoin('invites', 'invites.inviterDriverPhone', '=', 'd.phone')
|
||||
->leftJoin('shamCash', 'shamCash.driver_id', '=', 'd.id')
|
||||
->leftJoin('CarRegistration', 'CarRegistration.driverID', '=', 'd.id')
|
||||
->leftJoin('captain_bank', 'captain_bank.captain_id', '=', 'd.id')
|
||||
->select([
|
||||
'd.id', 'd.phone', 'd.email', 'd.gender', 'd.status',
|
||||
'd.first_name', 'd.last_name',
|
||||
'd.name_arabic',
|
||||
'phone_verification.verified as is_verified',
|
||||
'invites.isInstall',
|
||||
'shamCash.is_claimed',
|
||||
'CarRegistration.make', 'CarRegistration.model', 'CarRegistration.year',
|
||||
'captain_bank.bankCode', 'captain_bank.accountBank',
|
||||
])
|
||||
->where('d.id', $id)
|
||||
->first();
|
||||
|
||||
if (!$row) {
|
||||
return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']);
|
||||
}
|
||||
|
||||
$driver = (array) $row;
|
||||
|
||||
// Decrypt necessary fields
|
||||
$fieldsToDecrypt = ['email', 'phone', 'first_name', 'last_name', 'gender', 'name_arabic'];
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (!empty($driver[$field])) {
|
||||
$dec = $this->encryption->decrypt($driver[$field]);
|
||||
if ($dec) $driver[$field] = $dec;
|
||||
}
|
||||
}
|
||||
|
||||
// Default values for null fields
|
||||
$driver['is_verified'] = $driver['is_verified'] ?? 0;
|
||||
$driver['isInstall'] = $driver['isInstall'] ?? 0;
|
||||
$driver['is_claimed'] = $driver['is_claimed'] ?? 0;
|
||||
$driver['bankCode'] = $driver['bankCode'] ?? '';
|
||||
$driver['accountBank'] = $driver['accountBank'] ?? '';
|
||||
$driver['make'] = $driver['make'] ?? '';
|
||||
$driver['model'] = $driver['model'] ?? '';
|
||||
$driver['year'] = $driver['year'] ?? '';
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'count' => 1,
|
||||
'data' => [$driver]
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -214,19 +538,54 @@ class AuthController extends Controller
|
||||
|
||||
public function driverJwtHandshake(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['id' => 'required|string', 'fingerPrint' => 'required|string']);
|
||||
// Flutter sends the email in the 'password' field, plus 'id' and 'fingerPrint'
|
||||
$request->validate([
|
||||
'id' => 'required|string',
|
||||
'fingerPrint' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'fcm_token' => 'required|string'
|
||||
]);
|
||||
|
||||
$driver = Driver::find($request->input('id'));
|
||||
$driver = Driver::where('id', $request->input('id'))->first();
|
||||
if (!$driver) return $this->failure('User not found');
|
||||
|
||||
// Verify the email (sent as password from Flutter) matches
|
||||
$decryptedEmail = $this->encryption->decrypt($driver->email);
|
||||
if (!$decryptedEmail) {
|
||||
// Fallback if decryption fails (e.g. invalid IV)
|
||||
if ($driver->email !== $request->input('password')) {
|
||||
return $this->failure('Security mismatch: Invalid email verification (Decryption Failed)', 403);
|
||||
}
|
||||
} elseif ($decryptedEmail !== $request->input('password') && $driver->email !== $request->input('password')) {
|
||||
return $this->failure('Security mismatch: Invalid email verification', 403);
|
||||
}
|
||||
|
||||
// Security Check: Verify fingerprint matches stored token
|
||||
$storedToken = DB::connection('primary')->table('captainToken')
|
||||
->where('captain_id', $driver->id)
|
||||
->first();
|
||||
|
||||
if ($storedToken && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) {
|
||||
// If a fingerprint exists, verify it. Otherwise, save the new one (acts as first time)
|
||||
if ($storedToken) {
|
||||
if (!empty($storedToken->fingerPrint) && !hash_equals((string)$storedToken->fingerPrint, (string)$request->input('fingerPrint'))) {
|
||||
return $this->failure('Security mismatch: Invalid device fingerprint', 403);
|
||||
}
|
||||
// Update fingerprint and token
|
||||
DB::connection('primary')->table('captainToken')->where('captain_id', $driver->id)
|
||||
->update([
|
||||
'fingerPrint' => $request->input('fingerPrint'),
|
||||
'token' => $request->input('fcm_token'),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
} else {
|
||||
DB::connection('primary')->table('captainToken')->insert([
|
||||
'captain_id' => $driver->id,
|
||||
'fingerPrint' => $request->input('fingerPrint'),
|
||||
'token' => $request->input('fcm_token'),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($driver->api_key)) {
|
||||
$this->generateApiKeys($driver);
|
||||
|
||||
@@ -25,6 +25,20 @@ class OtpController extends Controller
|
||||
$this->encryption = $encryption;
|
||||
}
|
||||
|
||||
/** POST /v2/otp/driver/send */
|
||||
public function sendDriver(Request $request): JsonResponse
|
||||
{
|
||||
$request->merge(['user_type' => 'driver']);
|
||||
return $this->send($request);
|
||||
}
|
||||
|
||||
/** POST /v2/otp/driver/verify */
|
||||
public function verifyDriver(Request $request): JsonResponse
|
||||
{
|
||||
$request->merge(['user_type' => 'driver']);
|
||||
return $this->verify($request);
|
||||
}
|
||||
|
||||
/** POST /v2/otp/send */
|
||||
public function send(Request $request): JsonResponse
|
||||
{
|
||||
@@ -105,7 +119,9 @@ class OtpController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Send SMS/WhatsApp via external provider
|
||||
// Send WhatsApp message (especially for drivers changing devices)
|
||||
// $message = "Your Intaleq App verification code is: $otp";
|
||||
// $this->sendWhatsAppFromServer($phone, $message);
|
||||
|
||||
// Check if passenger exists to allow immediate login (V1 style)
|
||||
// We check both encrypted and raw phone with multiple formats (963... and 0...)
|
||||
@@ -272,16 +288,72 @@ class OtpController extends Controller
|
||||
/** GET /v2/otp/check-phone?phone=XXX */
|
||||
public function checkPhone(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['phone' => 'required|string']);
|
||||
$phone = $request->input('phone') ?? $request->query('phone');
|
||||
|
||||
if (!$phone) {
|
||||
return $this->failure('Phone parameter is missing', 400);
|
||||
}
|
||||
|
||||
// We check phone_verification table (Legacy V1 style)
|
||||
$verified = DB::connection('primary')->table('phone_verification')
|
||||
->where('phone_number', $request->input('phone'))
|
||||
->where('phone_number', $phone)
|
||||
->where('verified', 1) // In V1 it might be 'verified' or 'is_verified'
|
||||
->exists();
|
||||
|
||||
// Fallback for column name 'is_verified' if 'verified' fails
|
||||
if (!$verified) {
|
||||
try {
|
||||
$verified = DB::connection('primary')->table('phone_verification')
|
||||
->where('phone_number', $phone)
|
||||
->where('is_verified', 1)
|
||||
->exists();
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'data' => ['verified' => $verified],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send WhatsApp message using the available bot servers (Ported from V1 functions.php)
|
||||
*/
|
||||
private function sendWhatsAppFromServer($to, $message)
|
||||
{
|
||||
$servers = [
|
||||
"https://bot5.intaleq.xyz/send", // ramat bus
|
||||
"https://bot3.intaleq.xyz/send", // shahd
|
||||
];
|
||||
|
||||
$url = $servers[array_rand($servers)];
|
||||
|
||||
$payload = [
|
||||
"to" => $to,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
$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"
|
||||
],
|
||||
CURLOPT_TIMEOUT => 5 // Don't block API for too long
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
\Log::error("[sendWhatsAppFromServer] cURL Error on $url: $err");
|
||||
return false;
|
||||
}
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,11 @@ class ProfileController extends Controller
|
||||
// Rating
|
||||
$data['rating'] = $driver->getAverageRating();
|
||||
|
||||
// Wallet Balance
|
||||
$wallet = DB::connection('primary')->table('captain_wallet')
|
||||
->where('captain_id', $id)->first();
|
||||
$data['wallet_balance'] = $wallet->balance ?? '0.00';
|
||||
|
||||
// Ride count
|
||||
$data['ride_count'] = DB::connection('ride')->table('ride')
|
||||
->where('driver_id', $id)->where('status', 'finish')->count();
|
||||
|
||||
@@ -39,8 +39,25 @@ class WalletController extends Controller
|
||||
public function balance(Request $request): JsonResponse
|
||||
{
|
||||
$id = $request->attributes->get('_jwt_user_id');
|
||||
$userType = $request->attributes->get('_jwt_user_type');
|
||||
|
||||
if ($userType === 'driver') {
|
||||
$bal = DB::connection('primary')->table('captain_wallet')
|
||||
->where('captain_id', $id)->value('balance') ?? '0.00';
|
||||
} else {
|
||||
$bal = DB::connection('primary')->table('passengerWallet')
|
||||
->where('passenger_id', $id)->value('balance') ?? '0.00';
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success', 'data' => ['balance' => $bal]]);
|
||||
}
|
||||
|
||||
/** GET /v2/wallet/driver/balance (Explicit) */
|
||||
public function driverBalance(Request $request): JsonResponse
|
||||
{
|
||||
$id = $request->attributes->get('_jwt_user_id');
|
||||
$bal = DB::connection('primary')->table('captain_wallet')
|
||||
->where('captain_id', $id)->value('balance') ?? '0.00';
|
||||
|
||||
return response()->json(['status' => 'success', 'data' => ['balance' => $bal]]);
|
||||
}
|
||||
@@ -54,6 +71,11 @@ class WalletController extends Controller
|
||||
]);
|
||||
|
||||
$id = $request->attributes->get('_jwt_user_id');
|
||||
$userType = $request->attributes->get('_jwt_user_type');
|
||||
|
||||
if ($userType !== 'passenger') {
|
||||
return response()->json(['status' => 'failure', 'message' => 'Only passengers can add funds manually'], 403);
|
||||
}
|
||||
|
||||
DB::connection('primary')->beginTransaction();
|
||||
try {
|
||||
@@ -106,33 +128,46 @@ class WalletController extends Controller
|
||||
|
||||
$request->validate([
|
||||
'balance' => 'required|numeric|min:0',
|
||||
'passenger_id' => 'required|string',
|
||||
'user_id' => 'required|string',
|
||||
'type' => 'required|in:passenger,driver',
|
||||
]);
|
||||
|
||||
DB::connection('primary')->table('passengerWallet')
|
||||
->where('passenger_id', $request->input('passenger_id'))
|
||||
$table = $request->input('type') === 'driver' ? 'captain_wallet' : 'passengerWallet';
|
||||
$key = $request->input('type') === 'driver' ? 'captain_id' : 'passenger_id';
|
||||
|
||||
DB::connection('primary')->table($table)
|
||||
->where($key, $request->input('user_id'))
|
||||
->update(['balance' => $request->input('balance')]);
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
|
||||
|
||||
/** GET /v2/wallet/passenger/transactions */
|
||||
/** GET /v2/wallet/transactions */
|
||||
public function transactions(Request $request): JsonResponse
|
||||
{
|
||||
$id = $request->attributes->get('_jwt_user_id');
|
||||
$userType = $request->attributes->get('_jwt_user_type');
|
||||
$page = (int) $request->input('page', 1);
|
||||
$limit = min((int) $request->input('limit', 20), 50);
|
||||
|
||||
// Get from payments table (completed rides)
|
||||
$payments = DB::connection('primary')->table('payments')
|
||||
if ($userType === 'driver') {
|
||||
$transactions = DB::connection('primary')->table('payments')
|
||||
->where('driverID', $id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->skip(($page - 1) * $limit)
|
||||
->take($limit)
|
||||
->get();
|
||||
} else {
|
||||
$transactions = DB::connection('primary')->table('payments')
|
||||
->where('passengerID', $id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->skip(($page - 1) * $limit)
|
||||
->take($limit)
|
||||
->get();
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success', 'data' => $payments]);
|
||||
return response()->json(['status' => 'success', 'data' => $transactions]);
|
||||
}
|
||||
|
||||
/** POST /v2/wallet/passenger/token */
|
||||
|
||||
@@ -24,27 +24,39 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
$exceptions->render(function (\Throwable $e) {
|
||||
\Log::error('Unhandled Exception', [
|
||||
$exceptions->render(function (\Throwable $e, \Illuminate\Http\Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
\Log::error('API Exception: ' . get_class($e), [
|
||||
'message' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
if (config('app.debug')) {
|
||||
$status = 500;
|
||||
if ($e instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface) {
|
||||
$status = $e->getStatusCode();
|
||||
} elseif ($e instanceof \Illuminate\Validation\ValidationException) {
|
||||
return response()->json([
|
||||
'status' => 'failure',
|
||||
'message' => 'DEBUG ERROR: ' . $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
], 500);
|
||||
'message' => 'Validation error',
|
||||
'errors' => $e->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
$response = [
|
||||
'status' => 'failure',
|
||||
'message' => 'Internal server error',
|
||||
], 500);
|
||||
'message' => $e->getMessage() ?: 'Internal server error',
|
||||
];
|
||||
|
||||
if (config('app.debug')) {
|
||||
$response['exception'] = get_class($e);
|
||||
$response['file'] = $e->getFile();
|
||||
$response['line'] = $e->getLine();
|
||||
$response['trace'] = $e->getTrace();
|
||||
}
|
||||
|
||||
return response()->json($response, $status);
|
||||
}
|
||||
});
|
||||
})
|
||||
->create()
|
||||
|
||||
@@ -53,10 +53,10 @@ Route::prefix('v2/auth')->group(function () {
|
||||
Route::get('/passenger/login-google', [AuthController::class, 'passengerLoginGoogle']);
|
||||
|
||||
// Driver
|
||||
Route::post('/driver/login', [AuthController::class, 'driverLogin']);
|
||||
Route::match(['get', 'post'], '/driver/login', [AuthController::class, 'driverLogin']);
|
||||
Route::post('/driver/register', [AuthController::class, 'driverRegister']);
|
||||
Route::post('/driver/wallet-login', [AuthController::class, 'driverWalletLogin']);
|
||||
Route::get('/driver/login-google', [AuthController::class, 'driverLoginGoogle']);
|
||||
Route::match(['get', 'post'], '/driver/login-google', [AuthController::class, 'driverLoginGoogle']);
|
||||
|
||||
// Admin & Service
|
||||
Route::post('/admin/login', [AuthController::class, 'adminLogin']);
|
||||
@@ -73,9 +73,14 @@ Route::post('v2/admin/errors', [MiscController::class, 'logClientError']);
|
||||
Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () {
|
||||
Route::post('/send', [OtpController::class, 'send']);
|
||||
Route::post('/verify', [OtpController::class, 'verify']);
|
||||
|
||||
// Dedicated Driver OTP Endpoints (matches V1 sendOtpMessageDriver.php)
|
||||
Route::post('/driver/send', [OtpController::class, 'sendDriver']);
|
||||
Route::post('/driver/verify', [OtpController::class, 'verifyDriver']);
|
||||
|
||||
Route::post('/email/send', [OtpController::class, 'sendEmail']);
|
||||
Route::post('/email/verify', [OtpController::class, 'verifyEmail']);
|
||||
Route::get('/check-phone', [OtpController::class, 'checkPhone']);
|
||||
Route::match(['get', 'post'], '/check-phone', [OtpController::class, 'checkPhone']);
|
||||
});
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user