2026-09-24 17:27:58 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Account;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
use Illuminate\Validation\Rule;
|
|
|
|
|
use Illuminate\View\View;
|
2026-09-25 15:37:34 +03:00
|
|
|
use Modules\Core\Auth\Exceptions\OtpThrottledException;
|
|
|
|
|
use Modules\Core\Customer\Exceptions\EmailAlreadyTakenException;
|
|
|
|
|
use Modules\Core\Customer\Exceptions\InvalidEmailChangeCodeException;
|
|
|
|
|
use Modules\Core\Customer\Services\CustomerEmailChangeService;
|
2026-09-24 17:27:58 +03:00
|
|
|
|
|
|
|
|
/**
|
2026-09-25 15:37:34 +03:00
|
|
|
* Changing the login email — a thin wrapper over boboko-core's
|
|
|
|
|
* Customer\Services\CustomerEmailChangeService, which owns the actual
|
|
|
|
|
* request/confirm mechanics, throttling, pending-change storage, and
|
|
|
|
|
* mailables. This controller's own job is just the storefront's session-
|
|
|
|
|
* scoped "which email did I just ask to switch to" UI state (so the
|
|
|
|
|
* .code/.resend pages know which address to show/resend to) and
|
|
|
|
|
* translating the service's exceptions into the flash-message flow the
|
|
|
|
|
* views expect.
|
2026-09-24 17:27:58 +03:00
|
|
|
*/
|
|
|
|
|
class EmailController extends Controller
|
|
|
|
|
{
|
2026-09-25 15:37:34 +03:00
|
|
|
private const SESSION_KEY = 'pending_email_change';
|
2026-09-24 17:27:58 +03:00
|
|
|
|
|
|
|
|
public function edit(string $locale, Request $request): View
|
|
|
|
|
{
|
|
|
|
|
return view('account.email', ['user' => $request->user()]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
public function send(string $locale, Request $request, CustomerEmailChangeService $emailChange): RedirectResponse
|
2026-09-24 17:27:58 +03:00
|
|
|
{
|
|
|
|
|
$user = $request->user();
|
|
|
|
|
|
|
|
|
|
$request->merge(['email' => Str::lower(trim((string) $request->input('email')))]);
|
|
|
|
|
|
|
|
|
|
$validated = $request->validate([
|
|
|
|
|
'email' => [
|
|
|
|
|
'required',
|
|
|
|
|
'email',
|
|
|
|
|
'max:255',
|
|
|
|
|
Rule::notIn([$user->email]),
|
|
|
|
|
],
|
|
|
|
|
], [
|
|
|
|
|
'email.not_in' => __('storefront.account.email_same'),
|
|
|
|
|
]);
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
try {
|
|
|
|
|
$emailChange->request($user, $validated['email']);
|
|
|
|
|
} catch (EmailAlreadyTakenException) {
|
|
|
|
|
return back()->withInput()->withErrors(['email' => __('storefront.account.email_taken')]);
|
|
|
|
|
} catch (OtpThrottledException) {
|
2026-09-24 17:27:58 +03:00
|
|
|
return back()->withInput()->withErrors(['email' => __('storefront.auth.too_many_codes')]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
$request->session()->put(self::SESSION_KEY, $validated['email']);
|
|
|
|
|
|
2026-09-24 17:27:58 +03:00
|
|
|
return redirect()->route('account.email.code');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function code(string $locale, Request $request): View|RedirectResponse
|
|
|
|
|
{
|
2026-09-25 15:37:34 +03:00
|
|
|
$pendingEmail = $request->session()->get(self::SESSION_KEY);
|
2026-09-24 17:27:58 +03:00
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
if (! $pendingEmail) {
|
2026-09-24 17:27:58 +03:00
|
|
|
return redirect()->route('account.email.edit');
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
return view('account.email-code', ['email' => $pendingEmail]);
|
2026-09-24 17:27:58 +03:00
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
public function resend(string $locale, Request $request, CustomerEmailChangeService $emailChange): RedirectResponse
|
2026-09-24 17:27:58 +03:00
|
|
|
{
|
2026-09-25 15:37:34 +03:00
|
|
|
$pendingEmail = $request->session()->get(self::SESSION_KEY);
|
2026-09-24 17:27:58 +03:00
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
if (! $pendingEmail) {
|
2026-09-24 17:27:58 +03:00
|
|
|
return redirect()->route('account.email.edit');
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
try {
|
|
|
|
|
$emailChange->request($request->user(), $pendingEmail);
|
|
|
|
|
} catch (EmailAlreadyTakenException) {
|
|
|
|
|
$request->session()->forget(self::SESSION_KEY);
|
|
|
|
|
|
|
|
|
|
return redirect()->route('account.email.edit')
|
|
|
|
|
->withErrors(['email' => __('storefront.account.email_taken')]);
|
|
|
|
|
} catch (OtpThrottledException) {
|
2026-09-24 17:27:58 +03:00
|
|
|
return back()->withErrors(['code' => __('storefront.auth.too_many_codes')]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return back()->with('status', __('storefront.auth.code_resent'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
public function verify(string $locale, Request $request, CustomerEmailChangeService $emailChange): RedirectResponse
|
2026-09-24 17:27:58 +03:00
|
|
|
{
|
2026-09-25 15:37:34 +03:00
|
|
|
$pendingEmail = $request->session()->get(self::SESSION_KEY);
|
2026-09-24 17:27:58 +03:00
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
if (! $pendingEmail) {
|
2026-09-24 17:27:58 +03:00
|
|
|
return redirect()->route('account.email.edit');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$validated = $request->validate(['code' => ['required', 'digits:6']]);
|
|
|
|
|
|
2026-09-25 15:37:34 +03:00
|
|
|
try {
|
|
|
|
|
$emailChange->confirm($request->user(), $validated['code']);
|
|
|
|
|
} catch (EmailAlreadyTakenException) {
|
2026-09-24 17:27:58 +03:00
|
|
|
$request->session()->forget(self::SESSION_KEY);
|
|
|
|
|
|
|
|
|
|
return redirect()->route('account.email.edit')
|
|
|
|
|
->withErrors(['email' => __('storefront.account.email_taken')]);
|
2026-09-25 15:37:34 +03:00
|
|
|
} catch (InvalidEmailChangeCodeException) {
|
|
|
|
|
return back()->withErrors(['code' => __('storefront.auth.invalid_code')]);
|
2026-09-24 17:27:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$request->session()->forget(self::SESSION_KEY);
|
|
|
|
|
|
|
|
|
|
return redirect()->route('account')->with('status', __('storefront.account.email_changed'));
|
|
|
|
|
}
|
|
|
|
|
}
|