305 lines
9.6 KiB
PHP
305 lines
9.6 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace Modules\Customer\Http\Controllers;
|
||
|
|
|
||
|
|
use App\Helper\RestaurantHelper;
|
||
|
|
use App\Http\Controllers\Controller;
|
||
|
|
use App\Traits\Authenticatable;
|
||
|
|
use Carbon\Carbon;
|
||
|
|
use Exception;
|
||
|
|
use Illuminate\Http\JsonResponse;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\Auth;
|
||
|
|
use Illuminate\Support\Facades\Hash;
|
||
|
|
use Modules\Customer\Http\Requests\Auth\CustomerLoginRequest;
|
||
|
|
use Modules\Customer\Http\Requests\Auth\ProfileUpdateRequest;
|
||
|
|
use Modules\Customer\Models\OtpVerification;
|
||
|
|
use Modules\Restaurant\Models\Customer;
|
||
|
|
|
||
|
|
class CustomerAuthController extends Controller
|
||
|
|
{
|
||
|
|
use Authenticatable;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Step 1: Send OTP to phone (Create customer if not exists)
|
||
|
|
*/
|
||
|
|
public function customerLogin(CustomerLoginRequest $request)
|
||
|
|
{
|
||
|
|
$phone = $request->phone;
|
||
|
|
$restaurantId = $this->getRestaurantIdFromHeader($request);
|
||
|
|
if ($restaurantId instanceof JsonResponse) {
|
||
|
|
return $restaurantId;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if customer already exists
|
||
|
|
$customer = Customer::where('restaurant_id', $restaurantId)
|
||
|
|
->where('phone', $phone)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
// If customer not exists, create one
|
||
|
|
if (! $customer) {
|
||
|
|
$customer = Customer::create([
|
||
|
|
'name' => 'Customer '.substr($phone, -4),
|
||
|
|
'phone' => $phone,
|
||
|
|
'password' => Hash::make($phone), // temporary password
|
||
|
|
'restaurant_id' => $restaurantId,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate OTP
|
||
|
|
$otp = '1234'; // rand(100000, 999999);
|
||
|
|
$expiryTime = now()->addMinutes(5);
|
||
|
|
|
||
|
|
// Save or update OTP record
|
||
|
|
OtpVerification::updateOrCreate(
|
||
|
|
[
|
||
|
|
'restaurant_id' => $restaurantId,
|
||
|
|
'phone' => $phone,
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'otp' => $otp,
|
||
|
|
'expires_at' => $expiryTime,
|
||
|
|
'updated_at' => now(),
|
||
|
|
]
|
||
|
|
);
|
||
|
|
|
||
|
|
// TODO: Send OTP via SMS Gateway (mock for now)
|
||
|
|
// Muthofun::send($phone, "Your OTP is: $otp");
|
||
|
|
|
||
|
|
return $this->ResponseSuccess([
|
||
|
|
'phone' => $phone,
|
||
|
|
'otp' => $otp, // ⚠️ remove in production
|
||
|
|
], 'OTP sent successfully.');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Step 2: Verify OTP and Login
|
||
|
|
*/
|
||
|
|
public function customerOtpVerify(Request $request)
|
||
|
|
{
|
||
|
|
$restaurantId = $this->getRestaurantIdFromHeader($request);
|
||
|
|
if ($restaurantId instanceof JsonResponse) {
|
||
|
|
return $restaurantId;
|
||
|
|
}
|
||
|
|
|
||
|
|
$request->validate([
|
||
|
|
'phone' => 'required|numeric|exists:customers,phone',
|
||
|
|
'otp' => 'required',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$phone = $request->phone;
|
||
|
|
$otp = $request->otp;
|
||
|
|
|
||
|
|
// Fetch OTP record
|
||
|
|
$otpRecord = OtpVerification::where('restaurant_id', $restaurantId)
|
||
|
|
->where('phone', $phone)
|
||
|
|
->first();
|
||
|
|
if (! $otpRecord) {
|
||
|
|
return $this->ResponseError([], 'Invalid phone or OTP expired.', 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check expiry
|
||
|
|
if (now()->greaterThan($otpRecord->expires_at)) {
|
||
|
|
$otpRecord->update(['otp' => null]);
|
||
|
|
|
||
|
|
return $this->ResponseError([], 'OTP expired. Please request a new one.', 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check OTP validity
|
||
|
|
if ($otpRecord->otp != $otp) {
|
||
|
|
return $this->ResponseError([], 'Invalid OTP.', 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
// OTP valid → Find customer
|
||
|
|
$customer = Customer::where('restaurant_id', $restaurantId)
|
||
|
|
->where('phone', $phone)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if (! $customer) {
|
||
|
|
return $this->ResponseError([], 'Customer not found.', 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear OTP after success
|
||
|
|
$otpRecord->update(['otp' => null]);
|
||
|
|
|
||
|
|
// Create API token
|
||
|
|
$tokenResult = $customer->createToken('Customer Access Token');
|
||
|
|
|
||
|
|
return $this->ResponseSuccess([
|
||
|
|
'customer' => $customer,
|
||
|
|
'access_token' => $tokenResult->accessToken,
|
||
|
|
'expires_at' => $tokenResult->token->expires_at,
|
||
|
|
], 'Login successful.');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function customerOtpSent(Request $request)
|
||
|
|
{
|
||
|
|
$restaurantId = $this->getRestaurantIdFromHeader($request);
|
||
|
|
if ($restaurantId instanceof JsonResponse) {
|
||
|
|
return $restaurantId;
|
||
|
|
}
|
||
|
|
|
||
|
|
$request->validate([
|
||
|
|
'phone' => 'required|numeric|exists:customers,phone',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$phone = $request->phone;
|
||
|
|
// TODO
|
||
|
|
$expiryTime = now()->addMinutes(15); // OTP expiry = 2 minutes
|
||
|
|
$otp = rand(100000, 999999); // Generate random 6-digit OTP
|
||
|
|
|
||
|
|
// Check if an OTP record already exists for this phone
|
||
|
|
$otpRecord = OtpVerification::where('restaurant_id', $restaurantId)
|
||
|
|
->where('phone', $phone)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if ($otpRecord) {
|
||
|
|
// Check if existing OTP is still valid (not expired)
|
||
|
|
if (now()->lessThan($otpRecord->expires_at)) {
|
||
|
|
$remainingSeconds = now()->diffInSeconds($otpRecord->expires_at);
|
||
|
|
|
||
|
|
return $this->ResponseError([
|
||
|
|
'remaining_seconds' => $remainingSeconds,
|
||
|
|
], "An OTP has already been sent. Please wait {$remainingSeconds} seconds before requesting a new one.", 429);
|
||
|
|
}
|
||
|
|
|
||
|
|
// OTP expired → generate and resend a new one
|
||
|
|
$otpRecord->update([
|
||
|
|
'otp' => $otp,
|
||
|
|
'expires_at' => $expiryTime,
|
||
|
|
]);
|
||
|
|
} else {
|
||
|
|
// No record → create a new one
|
||
|
|
OtpVerification::create([
|
||
|
|
'phone' => $phone,
|
||
|
|
'otp' => $otp,
|
||
|
|
'expires_at' => $expiryTime,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Replace with your SMS sending logic
|
||
|
|
// Example: Muthofun::send($phone, "Your OTP is: $otp");
|
||
|
|
|
||
|
|
return $this->ResponseSuccess([
|
||
|
|
'phone' => $phone,
|
||
|
|
'otp' => $otp, // ⚠️ Remove in production
|
||
|
|
'expires_at' => $expiryTime->toDateTimeString(),
|
||
|
|
], 'OTP sent successfully.');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function customerProfile(Request $request)
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$customer = Customer::where('id', Auth::id())->first();
|
||
|
|
|
||
|
|
if ($customer) {
|
||
|
|
$permissions = $customer->role?->permissions->pluck('name')->toArray() ?? []; // Convert permissions to an array
|
||
|
|
|
||
|
|
$customer = [
|
||
|
|
'id' => $customer->id,
|
||
|
|
'name' => $customer->first_name.' '.$customer->last_name,
|
||
|
|
'email' => $customer->email,
|
||
|
|
'phone' => $customer->phone,
|
||
|
|
'image' => $customer->avatar,
|
||
|
|
'role' => $customer->role?->name ?? 'no-role-assign',
|
||
|
|
'status' => $customer->status,
|
||
|
|
'permissions' => $permissions,
|
||
|
|
];
|
||
|
|
|
||
|
|
return $this->responseSuccess($customer, 'Profile fetched successfully.');
|
||
|
|
} else {
|
||
|
|
return $this->responseError([], 'Profile not found.');
|
||
|
|
}
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return $this->responseError([], 'An error occurred while fetching the profile.');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function customerUpdate(ProfileUpdateRequest $request)
|
||
|
|
{
|
||
|
|
$customer = Auth::guard('customer')->user();
|
||
|
|
|
||
|
|
// Get validated data
|
||
|
|
$data = $request->validated();
|
||
|
|
|
||
|
|
// Avatar upload (first time + update)
|
||
|
|
if ($request->hasFile('avatar')) {
|
||
|
|
$data['avatar'] = fileUploader(
|
||
|
|
'customers/',
|
||
|
|
'png',
|
||
|
|
$request->file('avatar'),
|
||
|
|
$customer->avatar // old image (null for first upload)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill customer data
|
||
|
|
$customer->fill($data);
|
||
|
|
|
||
|
|
// Reset email verification if email changed
|
||
|
|
if ($customer->isDirty('email')) {
|
||
|
|
$customer->email_verified_at = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
$customer->save();
|
||
|
|
|
||
|
|
return $this->responseSuccess(
|
||
|
|
$customer->fresh(),
|
||
|
|
'Profile Update Successfully Done.'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function changePassword(Request $request)
|
||
|
|
{
|
||
|
|
$validated = $request->validateWithBag('updatePassword', [
|
||
|
|
'current_password' => ['required', 'current_password'],
|
||
|
|
'password' => ['required', 'confirmed'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$request->customer()->update([
|
||
|
|
'password' => Hash::make($validated['password']),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $this->ResponseSuccess([], 'Password Changes Successfully.');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function accountDeleted(Request $request)
|
||
|
|
{
|
||
|
|
$request->validateWithBag('updatePassword', [
|
||
|
|
'current_password' => ['required', 'current_password'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$request->customer()->update([
|
||
|
|
'deleted_at' => Carbon::now(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $this->ResponseSuccess([], 'Account Delete successfully.');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function customerLogout(Request $request)
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$result = $request->customer()->token()->revoke();
|
||
|
|
if ($result) {
|
||
|
|
return $this->ResponseSuccess([], 'Logout Success');
|
||
|
|
}
|
||
|
|
} catch (Exception $e) {
|
||
|
|
return $this->ResponseSuccess([], 'Logout Failed');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getRestaurantIdFromHeader(Request $request)
|
||
|
|
{
|
||
|
|
$domain = $request->header('X-Domain'); // Custom header key (Change if needed)
|
||
|
|
if (! $domain) {
|
||
|
|
return $this->responseError([], 'Domain header is missing.', 400);
|
||
|
|
}
|
||
|
|
|
||
|
|
$restaurantResponse = RestaurantHelper::getRestaurantIdByDomain($domain);
|
||
|
|
if (! $restaurantResponse['status']) {
|
||
|
|
return $this->responseError([], $restaurantResponse['error'], 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $restaurantResponse['restaurant_id'];
|
||
|
|
}
|
||
|
|
}
|