File: /var/www/console.fixgini.com/app/Http/Controllers/Authentication/RegisterSeller.php
<?php
namespace App\Http\Controllers\Authentication;
use App\Models\User;
use App\Models\Wallet;
use App\Models\Country;
use App\Mail\WelcomeMail;
use App\Models\SellerNin;
use Illuminate\Http\Request;
use App\Models\OtpVerification;
use App\Services\ActivityLogger;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\ValidationException;
class RegisterSeller extends Controller
{
public function register(Request $request)
{
try {
if (User::where('email', $request->email)->exists()) {
return response()->json([
'status' => 'error',
'message' => 'Email already exists.'
], 409); // Conflict status code
}
if (User::where('phone', $request->phone)->exists()) {
return response()->json([
'status' => 'error',
'message' => 'Phone already exists.'
], 409);
}
$validatedData = $request->validate([
"name" => "required|string|max:100",
"lastname" => "required|string|max:100",
"middlename" => "nullable|string|max:100",
"dob" => "required|string|max:100",
"email" => "required|unique:users,email",
"gender" => "required",
"ip_address" => "required",
"phone" => "required|digits:11",
"password" => "required|min:6",
"nationality_id" => "required|exists:countries,id",
"device_name" => "required",
'role' => 'required',
]);
DB::beginTransaction();
$ip = $validatedData['ip_address'];
$location = $this->getLocation($ip);
$user = User::create([
'name' => ucwords(strtolower($validatedData['name'])),
'lastname' => ucwords(strtolower($validatedData['lastname'])),
'email' => $validatedData['email'],
'latitude' => $location['latitude'] ?? '',
'longitude' => $location['longitude'] ?? '',
'state' => $location['state'] ?? '',
'city' => $location['city'] ?? '',
'phone' => $validatedData['phone'],
'nationality_id' => $validatedData['nationality_id'],
'role' => $validatedData['role'],
'status' => 'active',
'is_fingerprint' => false,
'profile_photo_url' => 'https://console.fixgini.com/icon.png',
'is_pin' => false,
'password' => Hash::make($validatedData['password']),
]);
if (!$user) {
throw new \Exception('User creation failed.');
}
$currency = Country::where('id', $validatedData['nationality_id'])->first();
$wallet = Wallet::create([
'user_id' => $user->id,
'currency' => $currency->symbol
]);
if (!$wallet) {
throw new \Exception('Wallet creation failed.');
}
SellerNin::create([
'user_id' => $user->id,
'first_name' => ucwords(strtolower($validatedData['name'])),
'last_name' => ucwords(strtolower($validatedData['lastname'])),
'middle_name' => isset($validatedData['middlename']) ? ucwords(strtolower($validatedData['middlename'])) : 'NA',
'dob' => $validatedData['dob'],
'gender' => $validatedData['gender'],
]);
$device = $validatedData['device_name'];
// Log the user activity
$activityLogger = app(ActivityLogger::class);
$activityLogger->log('Seller Account was created', $user->id, $user->role, $device);
// Send welcome email
$this->sendPhoneOtp($user);
Mail::to($validatedData['email'])->send(new WelcomeMail($user));
DB::commit();
return response()->json(['status' => 'success', 'user' => $user, 'message' => 'Registration successful.'], 200);
} catch (\Exception $e) {
Log::error($e->getMessage());
DB::rollBack();
return response()->json(['status' => 'error', 'message' => $e->getMessage()], 500);
}
}
private function getLocation($ip)
{
try {
$clientIp = $ip;
// Attempt the first API call to api.ip2location.io
$apiKey = env('IP2LOCATION_API_KEY');
$response = Http::get("https://api.ip2location.io/?key={$apiKey}&ip={$clientIp}&format=json");
if ($response->successful()) {
$data = $response->json();
$latitude = $data['latitude'] ?? '';
$longitude = $data['longitude'] ?? '';
// If lat/lng exists, perform reverse geocoding
if ($latitude && $longitude) {
return $this->reverseGeocode($latitude, $longitude);
}
} else {
// Check for specific error in the first API response
$error = $response->json('error') ?? [];
if (isset($error['error_code']) && $error['error_code'] === 10000) {
Log::warning('IP2Location API error: ' . $error['error_message']);
}
}
// Fallback to ipinfo.io API
$response = Http::get("https://ipinfo.io/{$clientIp}?token=55690b2a8bf492");
if ($response->successful()) {
$data = $response->json();
// Extract location data from ipinfo response
$location = explode(',', $data['loc'] ?? '');
$latitude = $location[0] ?? '';
$longitude = $location[1] ?? '';
// Include city and state in the response
return [
'city' => $data['city'] ?? '',
'state' => $data['region'] ?? '',
'country' => $data['country'] ?? '',
'latitude' => $latitude ?? '',
'longitude' => $longitude ?? '',
];
}
} catch (\Exception $e) {
Log::error('Location fetching error: ' . $e->getMessage());
}
// Return a default response if all attempts fail
return [
'city' => '',
'state' => '',
'country' => '',
'latitude' => '',
'longitude' => '',
];
}
private function reverseGeocode($latitude, $longitude)
{
try {
$response = Http::get('https://maps.googleapis.com/maps/api/geocode/json', [
'latlng' => "{$latitude},{$longitude}",
'key' => env('GOOGLE_API_KEY'),
]);
if ($response->successful()) {
$data = $response->json();
if (isset($data['results']) && count($data['results']) > 0) {
$components = $data['results'][0]['address_components'];
$locationInfo = [
'city' => null,
'state' => null,
'latitude' => $latitude,
'longitude' => $longitude,
];
foreach ($components as $component) {
if (in_array('locality', $component['types'])) {
$locationInfo['city'] = $component['long_name'];
}
if (in_array('administrative_area_level_1', $component['types'])) {
$locationInfo['state'] = $component['long_name'];
}
}
return $locationInfo;
} else {
return [
'error' => true,
'message' => 'No results found.',
];
}
} else {
return [
'error' => true,
'message' => 'Failed to retrieve location data.',
];
}
} catch (\Exception $e) {
return [
'error' => true,
'message' => $e->getMessage(),
];
}
}
private function sendPhoneOtp($user)
{
try {
$phone = $user['phone'];
// Validate phone number
if (!$phone) {
return response(['status' => 'failed', 'message' => 'Phone number is required'], 400);
}
// Format phone number
if (substr($phone, 0, 1) == '0') {
$Fphone = '234' . substr($phone, 1);
}
$otp = random_int(100000, 999999);
// Update or create OTP record
OtpVerification::updateOrCreate(
['phone' => $phone],
['otp' => $otp]
);
// // Prepare SMS body
$body = [
'api_key' => config('services.termii.api'),
'to' => $Fphone,
'from' => 'N-Alert',
'sms' => 'Hi there, your phone number confirmation code is ' . $otp . '. Thank you for choosing FixGini Services',
'type' => 'plain',
'channel' => 'dnd',
];
// Uncomment and complete the API call to send the OTP
$response = Http::post('https://api.ng.termii.com/api/sms/send', $body);
if ($response->successful()) {
return response(['status' => 'success', 'message' => 'OTP sent to phone number successfully'], 200);
} else {
Log::error($response->json());
return response(['status' => 'failed', 'message' => 'Unable to send OTP to phone successfully'], 500);
}
} catch (\Throwable $th) {
return response(['status' => 'failed', 'message' => 'Unable to send SMS ', 'data' => $th->getMessage()], 500);
}
}
public function phoneOtp(Request $request)
{
try {
$validatedData = $request->validate([
'phone' => 'required',
]);
$phone = $validatedData['phone'];
// Validate phone number
if (!$phone) {
return response(['status' => 'failed', 'message' => 'Phone number is required'], 400);
}
// Format phone number
if (substr($phone, 0, 1) == '0') {
$Fphone = '234' . substr($phone, 1);
}
$otp = random_int(100000, 999999);
// Update or create OTP record
OtpVerification::updateOrCreate(
['phone' => $phone],
['otp' => $otp]
);
// // Prepare SMS body
$body = [
'api_key' => config('services.termii.api'),
'to' => $Fphone,
'from' => 'N-Alert',
'sms' => 'Hi there, your phone number confirmation code is ' . $otp . '. Thank you for choosing FixGini Services',
'type' => 'plain',
'channel' => 'dnd',
];
// Uncomment and complete the API call to send the OTP
$response = Http::post('https://api.ng.termii.com/api/sms/send', $body);
if ($response->successful()) {
return response(['status' => 'success', 'message' => 'OTP sent to phone number successfully'], 200);
} else {
Log::error($response->json());
return response(['status' => 'failed', 'message' => 'Unable to send OTP to phone successfully'], 500);
}
} catch (\Throwable $th) {
return response(['status' => 'failed', 'message' => 'Unable to send SMS ', 'data' => $th->getMessage()], 500);
}
}
// public function verifyPhoneOtp(Request $request)
// {
// try {
// $validatedData = $request->validate([
// 'phone' => 'required|digits:11|exists:otp_verifications,phone',
// 'otp' => 'required|digits:6|exists:otp_verifications,otp',
// ], [
// 'phone.exists' => 'The phone is not correct.',
// 'otp.exists' => 'The otp is not correct.',
// 'phone.required' => 'The phone field is required.',
// 'otp.required' => 'The otp field is required.',
// 'phone.digits' => 'The phone must be 11 digits.',
// 'otp.digits' => 'The otp must be 6 digits.',
// ]);
// $phone = $validatedData['phone'];
// $otp = $validatedData['otp'];
// // Fetch the OTP record
// $otpRecord = OtpVerification::where([
// ['otp', '=', $otp],
// ['status', '=', 'unverified'],
// ['phone', '=', $phone],
// ])->latest()->first();
// if (!$otpRecord) {
// Log::info('Invalid OTP or OTP record not found for phone: ' . $phone);
// return response()->json(['message' => 'Invalid OTP. Please try again', 'status' => 'error'], 401);
// }
// DB::beginTransaction();
// try {
// // Update OTP record to verified
// $otpRecord->update([
// 'status' => 'verified'
// ]);
// // Find the user by phone
// $user = User::where('phone', $phone)->first();
// if (!$user) {
// DB::rollBack();
// Log::info('User not found for phone: ' . $phone);
// return response()->json(['message' => 'User not found', 'status' => 'error'], 404);
// }
// // Update user status to active
// $user->update([
// 'status' => 'active'
// ]);
// // Update SellerNin record if exists
// $updateseller = SellerNin::where('user_id', $user->id)->first();
// if ($updateseller) {
// $updateseller->update([
// 'phone_verified_at' => now(),
// 'status' => 'verified'
// ]);
// }
// //send token
// $token = $user->createToken('api_token')->plainTextToken;
// // Delete the OTP record after success
// $otpRecord->delete();
// DB::commit();
// return response()->json(['message' => 'Phone number verified', 'status' => 'success', 'token' => $token, 'user' => $user], 200);
// } catch (\Throwable $th) {
// DB::rollBack();
// Log::error('Transaction failed: ' . $th->getMessage());
// return response()->json(['status' => 'error', 'message' => 'Failed to verify phone number. Please try again.', 'details' => $th->getMessage()], 500);
// }
// } catch (ValidationException $e) {
// Log::error('Validation error: ' . $e->getMessage());
// return response()->json(['status' => 'error', 'message' => $e->getMessage()], 422);
// } catch (\Throwable $th) {
// Log::error('OTP verification error: ' . $th->getMessage());
// return response()->json(['status' => 'error', 'message' => 'Network Issue! Please try again later.', 'details' => $th->getMessage()], 500);
// }
// }
public function verifyPhoneOtp(Request $request)
{
try {
// Validate input
$validatedData = $request->validate([
'phone' => 'required|digits:11',
'otp' => 'required|digits:6',
], [
'phone.required' => 'The phone field is required.',
'phone.digits' => 'The phone must be exactly 11 digits.',
'otp.required' => 'The OTP field is required.',
'otp.digits' => 'The OTP must be exactly 6 digits.',
]);
$phone = $validatedData['phone'];
$otp = $validatedData['otp'];
// Find the most recent unverified OTP for this phone
$otpRecord = OtpVerification::where('phone', $phone)
->where('status', 'unverified')
->latest()
->first();
if (!$otpRecord) {
Log::info("No unverified OTP found for phone: $phone");
return response()->json([
'message' => 'No valid OTP found for this phone number. It may have expired or already been used.',
'status' => 'error'
], 401);
}
// Check if OTP matches
if ($otpRecord->otp !== $otp) {
Log::info("Invalid OTP attempt for phone: $phone");
return response()->json([
'message' => 'The OTP entered is incorrect. Please try again.',
'status' => 'error'
], 401);
}
DB::beginTransaction();
// Mark OTP as verified
$otpRecord->update(['status' => 'verified']);
// Update user status
$user = User::where('phone', $phone)->first();
if (!$user) {
DB::rollBack();
Log::info("User not found for phone: $phone");
return response()->json([
'message' => 'No user associated with this phone number.',
'status' => 'error'
], 404);
}
$user->update(['status' => 'active']);
// Update SellerNin if applicable
// $updateseller = SellerNin::where('user_id', $user->id)->first();
// if ($updateseller) {
// $updateseller->update([
// 'phone_verified_at' => now(),
// 'status' => 'verified'
// ]);
// }
// Create token
$token = $user->createToken('api_token')->plainTextToken;
// Remove OTP record
$otpRecord->delete();
DB::commit();
return response()->json([
'message' => 'Phone number successfully verified.',
'status' => 'success',
'token' => $token,
'user' => $user
], 200);
} catch (ValidationException $e) {
return response()->json([
'status' => 'error',
'message' => $e->validator->errors()->first()
], 422);
} catch (\Throwable $th) {
DB::rollBack();
Log::error('OTP verification failed: ' . $th->getMessage());
return response()->json([
'status' => 'error',
'message' => 'An unexpected error occurred. Please try again later.',
'details' => $th->getMessage()
], 500);
}
}
}