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,
]);
}
}