HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/AccountController.php
<?php

namespace App\Http\Controllers\Refactor;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Library\Cache\AppCache;
use App\Model\Language;
use App\Model\Setting;

class AccountController extends Controller
{
    protected function findOwnedInvoice(Request $request, string $invoiceUid): ?\App\Model\Invoice
    {
        $customer = $request->user()->customer;

        return $customer->invoices()->where('invoices.uid', $invoiceUid)->first();
    }

    /**
     * Get available languages for the language switcher.
     */
    public function languages(Request $request)
    {
        $customer = $request->user()->customer;
        $languages = Language::where('status', Language::STATUS_ACTIVE)->orderBy('name')->get();
        $currentLang = $customer->language;

        return view('refactor.pages.account.languages', [
            'languages' => $languages,
            'currentLang' => $currentLang,
        ]);
    }

    /**
     * API token page.
     */
    public function api(Request $request)
    {
        return view('refactor.pages.account.api', [
            'menu' => 'api',
        ]);
    }

    /**
     * Profile page.
     */
    public function profile(Request $request)
    {
        $user = $request->user();
        $customer = $user->customer;

        if ($request->isMethod('post')) {
            // Avatar is now owned by avatarUpload / avatarRemove AJAX endpoints —
            // form submit handles only the text fields below.
            $rules = [
                'first_name' => 'required',
                'last_name' => 'required',
                'timezone' => 'required',
                'language_id' => 'required',
            ];

            if (Setting::get('user.require_mobile_phone') == 'yes') {
                $rules['phone'] = ['required', 'regex:/^[\+0-9]{10,15}$/'];
            }

            $this->validate($request, $rules);

            $user->first_name = $request->first_name;
            $user->last_name = $request->last_name;
            $user->phone = $request->phone;

            if (!empty($request->password)) {
                $user->password = bcrypt($request->password);
            }

            $user->save();

            if ($request->user()->customer->can('updateProfile', $customer)) {
                $customer->fill($request->all());
                $customer->save();
            }

            $request->session()->flash('alert-success', trans('refactor/account.flash.profile_updated'));
            return redirect()->route('refactor.account.profile');
        }

        if (!empty($request->old())) {
            $customer->fill($request->old());
            $user->fill($request->old());
        }

        return view('refactor.pages.account.profile', [
            'customer' => $customer,
            'user' => $user,
            'menu' => 'profile',
        ]);
    }

    /**
     * Avatar AJAX — upload + immediately apply. No Save click required.
     * Gated by the same updateProfile policy as the legacy form path so a
     * read-only user cannot bypass it via the AJAX endpoint.
     */
    public function avatarUpload(Request $request)
    {
        $user = $request->user();
        if (!$user->can('updateProfile', $user->customer)) {
            abort(403);
        }

        $request->validate([
            'image' => 'required|image|max:5120', // 5 MB
        ]);

        $user->uploadProfileImage($request->file('image'));

        return response()->json([
            'ok'         => true,
            'avatar_url' => $user->getProfileImageUrl(),
        ]);
    }

    /**
     * Avatar AJAX — remove + immediately apply.
     */
    public function avatarRemove(Request $request)
    {
        $user = $request->user();
        if (!$user->can('updateProfile', $user->customer)) {
            abort(403);
        }

        $user->removeProfileImage();

        return response()->json(['ok' => true]);
    }

    /**
     * Contact information page.
     */
    public function contact(Request $request)
    {
        $customer = $request->user()->customer;
        $contact = $customer->contact;

        if (!$contact) {
            $contact = new \App\Model\Contact();
        }

        if ($request->isMethod('post')) {
            $this->validate($request, \App\Model\Contact::$rules);

            $contact->fill($request->all());

            if ($contact->save()) {
                $customer->contact_id = $contact->id;
                $customer->save();
                $request->session()->flash('alert-success', trans('refactor/account.flash.contact_updated'));
            }

            return redirect()->route('refactor.account.contact');
        }

        return view('refactor.pages.account.contact', [
            'customer' => $customer,
            'contact' => $contact->fill($request->old()),
            'menu' => 'contact',
        ]);
    }

    /**
     * Activity logs page.
     */
    public function logs(Request $request)
    {
        return view('refactor.pages.account.logs', [
            'menu' => 'logs',
        ]);
    }

    /**
     * Logs listing (AJAX).
     */
    public function logsListing(Request $request)
    {
        $customer = $request->user()->customer;
        $request->merge(['customer_id' => $customer->id]);

        $query = \App\Model\ActivityLog::where('customer_id', $customer->id);

        // Keyword search
        if (!empty(trim($request->keyword ?? ''))) {
            $query->where('subject_name', 'like', '%' . $request->keyword . '%');
        }

        // Type filter
        if (!empty($request->type)) {
            $query->where('subject_type', $request->type);
        }

        // Sort — McList sends 'sort' and 'sort_direction'
        $sortCol = in_array($request->sort, ['created_at', 'subject_name', 'type']) ? $request->sort : 'created_at';
        $sortDir = $request->sort_direction === 'asc' ? 'asc' : 'desc';
        $query->orderBy($sortCol, $sortDir);

        $logs = $query->with('user')->paginate($request->per_page ?? 20);

        return view('refactor.pages.account._logs_list', [
            'logs' => $logs,
        ]);
    }

    // Notification endpoints have moved to
    // app/Http/Controllers/Refactor/Account/NotificationController (per the
    // notifications redesign — see docs/notifications/README.md).

    /**
     * Resource usage / quota page.
     */
    public function quota(Request $request)
    {
        $customer = $request->user()->customer;
        $sub      = $customer->getCurrentActiveSubscription();
        $planId   = $sub?->plan_id;

        // ---- Quotas (used / limit) ----
        // limit() returns int|null|false (false=not-granted, null=unlimited, int=cap).
        // The _quota_card blade component consumes the union directly and handles
        // the 4-state display itself — controller passes through unchanged.
        $quotas = app(\App\Services\Plans\Quotas\QuotasService::class);
        $QK     = \App\Services\Plans\Quotas\QuotaKey::class;

        $maxLists        = $quotas->limit($customer, $QK::MAX_LISTS);
        $maxCampaigns    = $quotas->limit($customer, $QK::MAX_CAMPAIGNS);
        $maxSubscribers  = $quotas->limit($customer, $QK::MAX_SUBSCRIBERS);
        $maxAutomations  = $quotas->limit($customer, $QK::MAX_AUTOMATIONS);
        $maxUpload       = $quotas->limit($customer, $QK::MAX_UPLOAD_SIZE_TOTAL);
        $maxDomains      = $quotas->limit($customer, $QK::MAX_SENDING_DOMAINS);
        $maxUsers        = $quotas->limit($customer, $QK::MAX_USERS);

        $listsCount       = $customer->local()->listsCount();
        $campaignsCount   = $customer->local()->campaignsCount();
        $subscribersCount = AppCache::for($customer->local())->read('SubscriberCount', 0);
        $automationsCount = $customer->local()->automationsCount();
        $uploadCount      = $customer->totalUploadSize();
        $domainsCount     = $customer->local()->sendingDomainsCount();
        $usersCount       = $customer->users()->count();

        // ---- Credits (used / granted per cycle) ----
        // Both `granted` and `remaining` come from the snapshot — Plan
        // Versioning §10: a sub may pre-date a plan_credits row (admin
        // configured credit AFTER signup) or post-date a plan edit (admin
        // raised/lowered the cap mid-cycle). Reading granted from the live
        // plan would lie. Skipping by snapshot existence keeps the panel
        // consistent with canUse() — if there's no state row, this sub was
        // not granted the credit and the UI should not list it.
        $credits = [];
        if ($sub) {
            $creditsService = app(\App\Services\Plans\Credits\CreditsService::class);
            $subCreditMap   = $creditsService->creditsFromSnapshot($sub);
            foreach (\App\Services\Plans\Credits\CreditKey::cases() as $ck) {
                if (!array_key_exists($ck->value, $subCreditMap)) continue;

                $granted   = $subCreditMap[$ck->value];                        // null = unlimited
                $remaining = $creditsService->remainingSnapshot($sub, $ck);    // null = unlimited
                $used = ($granted === null || $remaining === null || (int) $remaining < 0)
                    ? null
                    : max(0, (int) $granted - (int) $remaining);

                $credits[] = [
                    'key'       => $ck->value,
                    'label'     => $ck->label(),
                    'used'      => $used,
                    'granted'   => $granted,
                    'remaining' => $remaining,
                ];
            }
        }

        // ---- Entitlements (boolean) ----
        $entitlementMap = ($sub && $sub->plan)
            ? app(\App\Services\Plans\Entitlements\EntitlementsService::class)->entitlementsOfPlan($sub->plan)
            : [];
        $entitlements = [];
        foreach (\App\Services\Plans\Entitlements\EntitlementKey::cases() as $ek) {
            $entitlements[] = [
                'key'     => $ek->value,
                'label'   => $ek->label(),
                'group'   => $ek->group(),
                'enabled' => (bool) ($entitlementMap[$ek->value] ?? false),
            ];
        }

        // ---- Rate limits ----
        $rateLimits = ($sub && $sub->plan)
            ? array_map(
                fn ($r) => [
                    'key'         => $r->rate_key,
                    'limit_value' => (int) $r->limit_value,
                    'time_amount' => (int) $r->time_amount,
                    'time_unit'   => $r->time_unit,
                ],
                array_values(app(\App\Services\Plans\RateLimits\RateLimitsService::class)->configsOfPlan($sub->plan))
            )
            : [];

        return view('refactor.pages.account.quota', [
            'menu' => 'quota',
            'maxLists' => $maxLists,
            'listsCount' => $listsCount,
            'maxCampaigns' => $maxCampaigns,
            'campaignsCount' => $campaignsCount,
            'maxSubscribers' => $maxSubscribers,
            'subscribersCount' => $subscribersCount,
            'maxAutomations' => $maxAutomations,
            'automationsCount' => $automationsCount,
            'maxUpload' => $maxUpload,
            'uploadCount' => $uploadCount,
            'maxDomains' => $maxDomains,
            'domainsCount' => $domainsCount,
            'maxUsers' => $maxUsers,
            'usersCount' => $usersCount,
            'credits' => $credits,
            'creditsLastSynced' => $sub
                ? \App\Model\SubscriptionCreditState::where('subscription_id', $sub->id)->max('last_synced_at')
                : null,
            'hasSubscription' => (bool) $sub,
            'entitlements' => $entitlements,
            'rateLimits' => $rateLimits,
        ]);
    }

    /**
     * Billing page.
     */
    public function billing(Request $request)
    {
        return view('refactor.pages.account.billing', [
            'customer' => $request->user()->customer,
            'menu' => 'billing',
        ]);
    }

    /**
     * Edit billing address (AJAX popup).
     */
    public function editBillingAddress(Request $request)
    {
        $customer = $request->user()->customer;
        $billingAddress = $customer->getDefaultBillingAddress();

        if (!$billingAddress) {
            $billingAddress = $customer->newBillingAddress();
        }

        if ($request->same_as_contact == 'true') {
            $billingAddress->copyFromContact();
        }

        if ($request->isMethod('post')) {
            list($validator, $billingAddress) = $billingAddress->updateAll($request);

            if ($validator->fails()) {
                return response()->view('refactor.pages.account._billing_address_form', [
                    'billingAddress' => $billingAddress,
                    'errors' => $validator->errors(),
                ], 400);
            }

            $request->session()->flash('alert-success', trans('refactor/account.flash.billing_updated'));
            return response()->json(['status' => 'success']);
        }

        return view('refactor.pages.account._billing_address_form', [
            'billingAddress' => $billingAddress,
        ]);
    }

    /**
     * Save color scheme preference (AJAX, returns JSON).
     */
    public function saveColorScheme(Request $request)
    {
        $customer = $request->user()->customer;
        $allowed = ['default', 'blue', 'green', 'brown', 'pink', 'grey', 'white'];

        $scheme = $request->input('color_scheme', 'default');
        if (!in_array($scheme, $allowed)) {
            $scheme = 'default';
        }

        $customer->color_scheme = $scheme;
        $customer->save();

        return response()->json(['status' => 'success', 'scheme' => $scheme]);
    }

    /**
     * Change the user's language (AJAX, returns JSON).
     */
    public function changeLanguage(Request $request)
    {
        $customer = $request->user()->customer;
        $language = Language::findByUid($request->uid);

        if (!$language) {
            return response()->json([
                'status' => 'error',
                'message' => trans('refactor/account.error.language_not_found'),
            ], 404);
        }

        $customer->language_id = $language->id;
        $customer->save();

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/account.flash.language_changed_to', ['name' => $language->name]),
            'code' => $language->code,
        ]);
    }

    /**
     * Renew API token.
     */
    public function renewToken(Request $request)
    {
        $user = $request->user();
        $user->api_token = str_random(60);
        $user->save();

        if ($request->ajax()) {
            return response()->json([
                'status' => 'success',
                'message' => trans('refactor/account.flash.token_renewed'),
                'token' => $user->api_token,
            ]);
        }

        $request->session()->flash('alert-success', trans('refactor/account.flash.token_renewed'));
        return redirect()->route('refactor.account.api');
    }

    /**
     * Save tour completion state (AJAX, returns JSON).
     */
    public function saveTourComplete(Request $request)
    {
        $customer = $request->user()->customer;
        $customer->app_tour_completed = true;
        $customer->save();

        return response()->json(['status' => 'success']);
    }

    /* ═══════════════════════════════════════════════════════
     |  USERS
     * ═══════════════════════════════════════════════════════ */

    public function users(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.account.users', ['menu' => 'users']);
    }

    public function usersListing(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return response('', 403);
        }

        $customer = $request->user()->customer;
        $users = $customer->users()
            ->search($request->keyword)
            ->orderBy($request->sort_order ?? 'created_at', $request->sort_direction ?? 'desc')
            ->paginate($request->per_page ?? 20);

        return view('refactor.pages.account._users_list', compact('users'));
    }

    public function userCreate(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $customer = $request->user()->customer;

        // Quota gate — canAccept() encapsulates the 4-state limit semantic.
        if (!app(\App\Services\Plans\Quotas\QuotasService::class)->canAccept(
            $customer, \App\Services\Plans\Quotas\QuotaKey::MAX_USERS, 1
        )) {
            return back()->withErrors(['quota' => trans('refactor/account.users.quota_reached')]);
        }

        $user = $customer->newUser();
        $roles = $customer->getSelectableRoles();

        return view('refactor.pages.account.user_form', [
            'menu'  => 'users',
            'user'  => $user,
            'roles' => $roles,
            'mode'  => 'create',
        ]);
    }

    public function userStore(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $user = $request->user()->customer->newUser();

        [$result, $errors] = $user->saveUser(
            $request->email,
            $request->password,
            $request->password_confirmation,
            $request->first_name,
            $request->last_name,
            $request->image,
            $request->role_uid,
            $request->phone
        );

        if (!$result) {
            return back()->withInput()->withErrors($errors);
        }

        $request->session()->flash('alert-success', trans('refactor/account.users.flash.created'));
        return redirect()->route('refactor.account.users');
    }

    public function userEdit(Request $request, $uid)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $user = \App\Model\User::findByUid($uid);
        if (!$user) {
            abort(404);
        }

        $roles = $request->user()->customer->getSelectableRoles();

        return view('refactor.pages.account.user_form', [
            'menu'  => 'users',
            'user'  => $user,
            'roles' => $roles,
            'mode'  => 'edit',
        ]);
    }

    public function userUpdate(Request $request, $uid)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $user = \App\Model\User::findByUid($uid);
        if (!$user) {
            abort(404);
        }

        // Admin role check
        if ($request->role_uid) {
            $role = \App\Model\Role::findByUid($request->role_uid);
            if ($role && $role->code !== 'organization_admin') {
                $exists = $user->customer->users()
                    ->where('id', '!=', $user->id)
                    ->whereHas('roles', fn($q) => $q->whereCode('organization_admin'))
                    ->exists();
                if (!$exists) {
                    return back()->withInput()->withErrors(['error' => trans('messages.account.at_least_one_admin_user')]);
                }
            }
        }

        [$result, $errors] = $user->saveUser(
            $request->email,
            $request->password,
            $request->password_confirmation,
            $request->first_name,
            $request->last_name,
            $request->image,
            $request->role_uid,
            $request->phone
        );

        if (!$result) {
            return back()->withInput()->withErrors($errors);
        }

        if ($request->_remove_image === 'true') {
            $user->removeProfileImage();
        }

        $request->session()->flash('alert-success', trans('refactor/account.users.flash.updated'));
        return redirect()->route('refactor.account.users');
    }

    public function userEnable(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        foreach (\App\Model\User::whereIn('uid', $uids)->get() as $user) {
            if ($user->customer->can('enable', $user)) {
                $user->enable();
            }
        }
        return response(trans('refactor/account.users.flash.enabled'));
    }

    public function userDisable(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        foreach (\App\Model\User::whereIn('uid', $uids)->get() as $user) {
            if ($user->customer->can('disable', $user)) {
                $user->disable();
            }
        }
        return response(trans('refactor/account.users.flash.disabled'));
    }

    public function userDelete(Request $request)
    {
        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (\App\Model\User::whereIn('uid', $uids)->get() as $user) {
            if ($user->customer->can('delete', $user)) {
                $user->delete();
                $deleted++;
            }
        }
        return response($deleted . ' ' . trans('refactor/account.users.flash.deleted'));
    }

    /* ═══════════════════════════════════════════════════════
     |  ROLES
     * ═══════════════════════════════════════════════════════ */

    public function roles(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        return view('refactor.pages.account.roles', ['menu' => 'roles']);
    }

    public function rolesListing(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return response('', 403);
        }

        $roles = $request->user()->customer->ownOrGlobalRoles()
            ->orderBy($request->sort_order ?? 'created_at', $request->sort_direction ?? 'desc')
            ->paginate($request->per_page ?? 20);

        return view('refactor.pages.account._roles_list', compact('roles'));
    }

    public function roleCreate(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $role = \App\Model\Role::newAccountRole($request->user()->customer);

        return view('refactor.pages.account.role_form', [
            'menu' => 'roles',
            'role' => $role,
            'mode' => 'create',
        ]);
    }

    public function roleStore(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $role = \App\Model\Role::newAccountRole($request->user()->customer);

        $errors = $role->saveRole(
            $request->name,
            $request->description,
            $request->permissions
        );

        if (!$errors->isEmpty()) {
            return back()->withInput()->withErrors($errors);
        }

        $request->session()->flash('alert-success', trans('refactor/account.roles.flash.created'));
        return redirect()->route('refactor.account.roles');
    }

    public function roleEdit(Request $request, $uid)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $role = \App\Model\Role::findByUid($uid);
        if (!$role) {
            abort(404);
        }

        return view('refactor.pages.account.role_form', [
            'menu' => 'roles',
            'role' => $role,
            'mode' => 'edit',
        ]);
    }

    public function roleUpdate(Request $request, $uid)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $role = \App\Model\Role::findByUid($uid);
        if (!$role || !$request->user()->customer->can('update', $role)) {
            return $this->notAuthorized();
        }

        $errors = $role->saveRole(
            $request->name,
            $request->description,
            $request->permissions
        );

        if (!$errors->isEmpty()) {
            return back()->withInput()->withErrors($errors);
        }

        $request->session()->flash('alert-success', trans('refactor/account.roles.flash.updated'));
        return redirect()->route('refactor.account.roles');
    }

    public function roleDelete(Request $request)
    {
        if (!$request->user()->hasPermission('account.full_access')) {
            return $this->notAuthorized();
        }

        $uids = is_array($request->uids) ? $request->uids : explode(',', $request->uids);
        $deleted = 0;
        foreach (\App\Model\Role::whereIn('uid', $uids)->get() as $role) {
            if ($request->user()->customer->can('delete', $role)) {
                $role->delete();
                $deleted++;
            }
        }

        if ($request->ajax()) {
            return response()->json(['message' => $deleted . ' ' . trans('refactor/account.roles.flash.deleted')]);
        }

        $request->session()->flash('alert-success', $deleted . ' ' . trans('refactor/account.roles.flash.deleted'));
        return redirect()->route('refactor.account.roles');
    }

    /* ═══════════════════════════════════════════════════════
     |  TWO-FACTOR AUTHENTICATION
     * ═══════════════════════════════════════════════════════ */

    public function twoFactor(Request $request)
    {
        $user = $request->user();
        if (\App\Model\Setting::get('2fa_enable') != 'yes') {
            abort(404);
        }
        return view('refactor.pages.account.two_factor', ['menu' => '2fa', 'user' => $user]);
    }

    public function twoFactorEnable(Request $request)
    {
        $user = $request->user();
        if (\App\Model\Setting::get('2fa_enable') != 'yes') {
            return response()->json(['status' => 'error', 'message' => '2FA not available'], 403);
        }
        $user->enable_2fa = true;
        $user->enable_2fa_email = (bool) $request->input('enable_email', false);
        $user->enable_2fa_google_authenticator = (bool) $request->input('enable_google', false);
        $user->save();
        return response()->json(['status' => 'success', 'message' => trans('refactor/account.2fa.flash.enabled')]);
    }

    public function twoFactorDisable(Request $request)
    {
        $user = $request->user();
        if (\App\Model\Setting::get('2fa_enable') != 'yes') {
            return response()->json(['status' => 'error', 'message' => '2FA not available'], 403);
        }
        $user->enable_2fa = false;
        $user->enable_2fa_email = false;
        $user->enable_2fa_google_authenticator = false;
        $user->save();
        return response()->json(['status' => 'success', 'message' => trans('refactor/account.2fa.flash.disabled')]);
    }

    public function twoFactorGenerateKey(Request $request)
    {
        $user = $request->user();
        if (\App\Model\Setting::get('2fa_enable') != 'yes') {
            return response()->json(['status' => 'error', 'message' => '2FA not available'], 403);
        }
        $key = \Google2FA::generateSecretKey();
        $qrCodeInline = \Google2FA::getQRCodeInline(\App\Model\Setting::get('site_name'), $user->email, $key);
        return response()->json(['status' => 'success', 'key' => $key, 'qr_code' => $qrCodeInline]);
    }

    public function twoFactorSaveKey(Request $request)
    {
        $user = $request->user();
        if (\App\Model\Setting::get('2fa_enable') != 'yes') {
            return response()->json(['status' => 'error', 'message' => '2FA not available'], 403);
        }
        $key = $request->input('key');
        $code = $request->input('code');
        if (!$key || !$code) {
            return response()->json(['status' => 'error', 'message' => 'Key and code are required'], 422);
        }
        if (!\Google2FA::verifyKey($key, $code)) {
            return response()->json(['status' => 'error', 'message' => trans('refactor/account.2fa.error.invalid_code')], 422);
        }
        $user->google_2fa_secret_key = $key;
        $user->save();
        return response()->json(['status' => 'success', 'message' => trans('refactor/account.2fa.flash.key_saved')]);
    }

    /* ═══════════════════════════════════════════════════════
     |  SUBSCRIPTION
     * ═══════════════════════════════════════════════════════ */

    public function subscription(Request $request)
    {
        $customer = $request->user()->customer;
        $subscription = $customer->getNewOrActiveSubscription();

        // No subscription → select plan
        if (!$subscription) {
            return redirect()->route('refactor.subscription.select_plan');
        }

        // Plan deactivated by admin
        if (!$subscription->plan->isActive()) {
            return response()->view('errors.general', [
                'message' => __('messages.subscription.error.plan-not-active', ['name' => $subscription->plan->name]),
            ]);
        }

        // NEW subscription → resume payment flow
        if ($subscription->isNew()) {
            $order = $subscription->getItsOnlyUnpaidInitOrder();
            $invoice = $order ? $order->getItsOnlyInvoice() : null;

            if ($invoice) {
                $request->session()->reflash();
                return redirect()->route('refactor.subscription.payment', [
                    'invoice_uid' => $invoice->uid,
                ]);
            }

            // Broken state: new subscription without invoice → select plan
            return redirect()->route('refactor.subscription.select_plan');
        }

        // ACTIVE subscription → show overview
        // Credits remaining/granted are read inline by the view's $tabItems block.
        return view('refactor.pages.account.subscription', [
            'menu'         => 'subscription',
            'customer'     => $customer,
            'subscription' => $subscription,
            'plan'         => $subscription->plan,
        ]);
    }

    /**
     * AJAX: refresh credit DB snapshot from live tracker for the current customer's
     * active subscription. Backs the "↻" icon next to credit displays.
     */
    public function refreshCredits(Request $request)
    {
        $sub = $request->user()->customer->getNewOrActiveSubscription();
        if (!$sub) {
            return response()->json(['error' => 'No active subscription'], 404);
        }

        $credits = app(\App\Services\Plans\Credits\CreditsService::class)
            ->syncSnapshotFromTracker($sub);

        return response()->json([
            'credits'        => $credits,
            'last_synced_at' => now()->toIso8601String(),
        ]);
    }

    /* ═══════════════════════════════════════════════════════
     |  EMAIL VERIFICATION CREDITS
     * ═══════════════════════════════════════════════════════ */

    /**
     * Email verification credits — overview + purchase history
     */
    public function emailVerificationCredits(Request $request)
    {
        return redirect()->route('refactor.account.subscription', ['tab' => 'email_verification']);
    }

    /**
     * Email verification credits — select plan popup
     */
    public function emailVerificationSelectPlan(Request $request)
    {
        $plans = \App\Model\EmailVerificationPlan::visible()->orderBy('credits', 'asc')->get();

        return view('refactor.pages.account.email_verification_select', [
            'plans' => $plans,
        ]);
    }

    /**
     * Email verification credits — invoices listing (AJAX)
     */
    public function emailVerificationInvoices(Request $request)
    {
        $invoices = $request->user()->customer->emailVerificationCreditsInvoices()->select('invoices.*');

        if ($request->status) {
            if ($request->status == 'pending') {
                $invoices = $invoices->pending();
            } else {
                $invoices = $invoices->notPending()->where('status', $request->status);
            }
        }

        $invoices = $invoices->orderBy('invoices.created_at', 'desc')->paginate($request->per_page ?? 10);

        return view('refactor.pages.account._email_verification_invoices', [
            'invoices' => $invoices,
        ]);
    }

    /**
     * Email verification credits — payment intents listing (AJAX)
     */
    public function emailVerificationPaymentIntents(Request $request)
    {
        $intents = $request->user()->customer->emailVerificationCreditsPaymentIntents();

        if ($request->status) {
            $intents = $intents->where('status', $request->status);
        }

        $intents = $intents->orderBy('created_at', 'desc')->paginate($request->per_page ?? 10);

        return view('refactor.pages.account._email_verification_payment_intents', [
            'intents' => $intents,
        ]);
    }

    /* ═══════════════════════════════════════════════════════
     |  SENDING CREDITS
     * ═══════════════════════════════════════════════════════ */

    /**
     * Sending credits add-on — overview + purchase history
     */
    public function sendingCredits(Request $request)
    {
        return redirect()->route('refactor.account.subscription', ['tab' => 'sending_credits']);
    }

    /**
     * Sending credits — select plan popup
     */
    public function sendingCreditsSelectPlan(Request $request)
    {
        $plans = \App\Model\SendingCreditPlan::visible()->orderBy('credits', 'asc')->get();

        return view('refactor.pages.account.sending_credits_select', [
            'plans' => $plans,
        ]);
    }

    /**
     * Sending credits — invoices listing (AJAX)
     */
    public function sendingCreditsInvoices(Request $request)
    {
        $invoices = $request->user()->customer->sendingCreditsInvoices()->select('invoices.*');

        if ($request->status) {
            if ($request->status == 'pending') {
                $invoices = $invoices->pending();
            } else {
                $invoices = $invoices->notPending()->where('status', $request->status);
            }
        }

        $invoices = $invoices->orderBy('invoices.created_at', 'desc')->paginate($request->per_page ?? 10);

        return view('refactor.pages.account._sending_credits_invoices', [
            'invoices' => $invoices,
        ]);
    }

    /**
     * Sending credits — payment intents listing (AJAX)
     */
    public function sendingCreditsPaymentIntents(Request $request)
    {
        $intents = $request->user()->customer->sendingCreditsPaymentIntents();

        if ($request->status) {
            $intents = $intents->where('status', $request->status);
        }

        $intents = $intents->orderBy('created_at', 'desc')->paginate($request->per_page ?? 10);

        return view('refactor.pages.account._sending_credits_payment_intents', [
            'intents' => $intents,
        ]);
    }

    // ─── Proxy methods ──────────────────────────────────────────

    public function invoiceDownload(Request $request, $uid)
    {
        return app(\App\Http\Controllers\InvoiceController::class)->download($request, $uid);
    }

    public function invoiceLogs(Request $request, $uid)
    {
        $invoice = $this->findOwnedInvoice($request, $uid);

        if (!$invoice) {
            abort(404);
        }

        return view('refactor.pages.account._invoice_logs', [
            'invoice' => $invoice,
        ]);
    }

    public function invoiceDelete(Request $request, $uid)
    {
        $invoice = $this->findOwnedInvoice($request, $uid);

        if (!$invoice) {
            abort(404);
        }

        if ($request->user()->customer->can('delete', $invoice)) {
            app(\App\Library\OrderFulfillment\FulfillmentService::class)->cancelInvoice($invoice);
        }

        return response()->json([
            'status' => 'success',
            'message' => trans('refactor/account.invoice.deleted'),
        ]);
    }

    public function buyEmailVerificationPlan(Request $request)
    {
        $customer = $request->user()->customer;
        $plan = \App\Model\EmailVerificationPlan::findByUid($request->plan_uid);

        $order = app(\App\Services\Subscription\SubscriptionManagementService::class)
            ->createEmailVerificationCreditsOrder(
                $customer,
                $plan->getPrice(),
                $plan->currency,
                $plan->credits,
            );

        return redirect()->route('refactor.checkout.billing_address', [
            'invoice_uid' => $order->getItsOnlyInvoice()->uid,
        ]);
    }

    public function buySendingCreditPlan(Request $request)
    {
        $customer = $request->user()->customer;
        $plan = \App\Model\SendingCreditPlan::findByUid($request->plan_uid);

        $order = app(\App\Services\Subscription\SubscriptionManagementService::class)
            ->createSendingCreditsOrder(
                $customer,
                $plan->getPrice(),
                $plan->currency,
                $plan->credits,
            );

        return redirect()->route('refactor.checkout.billing_address', [
            'invoice_uid' => $order->getItsOnlyInvoice()->uid,
        ]);
    }
}