refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Helpers\AccountHelper;
use App\Helpers\AuditLogHelper;
use App\Http\Controllers\Controller;
class AuditLogController extends Controller
{
/**
* Display the page listing all the audit logs.
*/
public function index()
{
$logs = auth()->user()->account->auditLogs()
->with('author')
->orderBy('created_at', 'desc')
->paginate(15);
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('settings.auditlog.index')
->withLogsCollection(AuditLogHelper::getCollectionOfAudits($logs))
->withAccountHasLimitations($accountHasLimitations)
->withLogsPagination($logs);
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Jobs\ExportAccount;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Helpers\StorageHelper;
use App\Models\Account\ExportJob;
use App\Http\Controllers\Controller;
class ExportController extends Controller
{
/**
* Display the export view.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$exports = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
])
->orderByDesc('created_at')
->get();
return view('settings.export')
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withExports($exports);
}
/**
* Exports the data of the account in SQL format.
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function storeSql()
{
$job = $this->newExport(ExportJob::SQL);
ExportAccount::dispatch($job);
return redirect()->route('settings.export.index')
->withStatus(trans('settings.export_submitted'));
}
/**
* Exports the data of the account in SQL format.
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function storeJson()
{
$job = $this->newExport(ExportJob::JSON);
ExportAccount::dispatch($job);
return redirect()->route('settings.export.index')
->withStatus(trans('settings.export_submitted'));
}
/**
* Create a new ExportJob.
*
* @param string $type
* @return ExportJob
*/
private function newExport(string $type): ExportJob
{
$exports = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
])
->orderBy('created_at')
->get();
if ($exports->count() >= config('monica.export_size')) {
$job = $exports->first();
try {
if ($job->filename !== null) {
StorageHelper::disk($job->location)
->delete($job->filename);
}
} finally {
$job->delete();
}
}
return ExportJob::create([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'type' => $type,
]);
}
/**
* Download the generated file.
*
* @param Request $request
* @param string $uuid
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function download(Request $request, string $uuid)
{
$job = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'uuid' => $uuid,
])->firstOrFail();
if ($job->status !== ExportJob::EXPORT_DONE) {
return redirect()->route('settings.export.index')
->withErrors(trans('settings.export_not_done'));
}
$disk = StorageHelper::disk($job->location);
return $disk->response($job->filename,
"monica.{$job->type}",
[
'Content-Type' => "application/{$job->type}; charset=utf-8",
'Content-Disposition' => "attachment; filename=monica.{$job->type}",
]
);
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Helpers\GenderHelper;
use App\Models\Contact\Gender;
use Illuminate\Validation\Rule;
use App\Helpers\CollectionHelper;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\Settings\GendersRequest;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class GendersController extends Controller
{
use JsonRespondController;
/**
* Get all the gender types.
*/
public function index()
{
$gendersData = collect([]);
$genders = auth()->user()->account->genders;
foreach ($genders as $gender) {
$gendersData->push($this->formatData($gender));
}
return CollectionHelper::sortByCollator($gendersData, 'name');
}
/**
* Get all the gender sex types.
*/
public function types()
{
$gendersData = collect([]);
$types = [
Gender::MALE,
Gender::FEMALE,
Gender::OTHER,
Gender::UNKNOWN,
Gender::NONE,
];
foreach ($types as $type) {
$gendersData->push([
'id' => $type,
'name' => trans('settings.personalization_genders_'.strtolower($type)),
]);
}
return CollectionHelper::sortByCollator($gendersData, 'name');
}
/**
* Store the gender.
*/
public function store(Request $request)
{
Validator::make($request->all(), [
'name' => 'required|max:255',
'type' => ['required', Rule::in(Gender::LIST)],
])->validate();
$gender = auth()->user()->account->genders()->create(
$request->only([
'name',
'type',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
if ($request->input('isDefault')) {
$this->updateDefault($gender);
}
return $this->formatData($gender);
}
/**
* Update the given gender.
*/
public function update(GendersRequest $request, Gender $gender)
{
$gender->update(
$request->only([
'name',
'type',
])
);
if ($request->input('isDefault')) {
$this->updateDefault($gender);
$gender->refresh();
} elseif ($gender->isDefault()) {
// Case of this gender was the default one previously
$account = auth()->user()->account;
$account->default_gender_id = null;
$account->save();
$gender->refresh();
}
return $this->formatData($gender);
}
/**
* Destroy a gender type.
*/
public function destroyAndReplaceGender(Gender $gender, $genderId)
{
$account = auth()->user()->account;
try {
$genderToReplaceWith = Gender::where('account_id', $account->id)
->findOrFail($genderId);
} catch (ModelNotFoundException $e) {
return response()->json([
'message' => trans('settings.personalization_genders_modal_error'),
], 403);
}
// We get the new gender to associate the contacts with.
GenderHelper::replace($account, $gender, $genderToReplaceWith);
if ($gender->isDefault()) {
$account->default_gender_id = $genderToReplaceWith->id;
$account->save();
}
$gender->delete();
return $this->respondObjectDeleted($gender->id);
}
/**
* Destroy a gender type.
*/
public function destroy(Gender $gender)
{
$gender->delete();
return $this->respondObjectDeleted($gender->id);
}
/**
* Update the given gender to the default gender.
*/
public function updateDefault(Gender $gender)
{
$account = auth()->user()->account;
$account->default_gender_id = $gender->id;
$account->save();
return $this->formatData($gender);
}
/**
* Format data for output.
*
* @param Gender $gender
* @return array
*/
private function formatData($gender)
{
return [
'id' => $gender->id,
'name' => $gender->name,
'type' => $gender->type,
'isDefault' => $gender->isDefault(),
'numberOfContacts' => $gender->contacts->count(),
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Models\User\Module;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
class ModulesController extends Controller
{
use JsonRespondController;
/**
* Get all the reminder rules.
*/
public function index()
{
$modules = auth()->user()->account->modules;
return $modules->map(function ($module) {
return $this->format($module);
});
}
public function toggle(Request $request, Module $module)
{
$module->active = ! $module->active;
$module->save();
return $this->respond([
'data' => $this->format($module),
]);
}
private function format(Module $module)
{
return [
'id' => $module->id,
'key' => $module->key,
'name' => trans($module->translation_key),
'active' => $module->active,
];
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Models\User\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use Illuminate\Foundation\Auth\RedirectsUsers;
use PragmaRX\Google2FALaravel\Facade as Google2FA;
use PragmaRX\Google2FALaravel\Support\Authenticator;
class MultiFAController extends Controller
{
use RedirectsUsers, JsonRespondController;
/**
* @var string
*/
protected $redirectTo = '/settings/security';
/**
* Session var name to store secret code.
*
* @var string
*/
private $SESSION_TFA_SECRET = '2FA_secret';
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function enableTwoFactor(Request $request)
{
//generate new secret
$secret = $this->generateSecret();
$user = $request->user();
//generate image for QR barcode
$imageDataUri = Google2FA::getQRCodeInline(
$request->getHttpHost(),
$user->email,
$secret,
200
);
$request->session()->put($this->SESSION_TFA_SECRET, $secret);
return response()->json(['image' => $imageDataUri, 'secret' => $secret]);
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function validateTwoFactor(Request $request)
{
//get user
$user = $request->user();
if (! is_null($user->google2fa_secret)) {
return response()->json(['error' => trans('settings.2fa_enable_error_already_set')]);
}
$this->validate($request, [
'one_time_password' => 'required',
]);
//retrieve secret
$secret = $request->session()->pull($this->SESSION_TFA_SECRET);
$authenticator = app(Authenticator::class)->boot($request);
if ($authenticator->verifyGoogle2FA($secret, $request['one_time_password'])) {
//encrypt and then save secret
$user->google2fa_secret = $secret;
$user->save();
$authenticator->login();
return response()->json(['success' => true]);
}
$authenticator->logout();
return response()->json(['success' => false]);
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function disableTwoFactor(Request $request)
{
return view('settings.security.2fa-disable');
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function deactivateTwoFactor(Request $request)
{
$this->validate($request, [
'one_time_password' => 'required',
]);
$user = $request->user();
if ($this->validateTwoFactorLogin($request, $user, $request['one_time_password'])) {
//make secret column blank
$user->google2fa_secret = null;
$user->save();
return response()->json(['success' => true]);
}
return response()->json(['success' => false]);
}
/**
* Validate 2nd factor for user with 2FA code or recovery code.
*
* @param Request $request
* @param User $user
* @param string $oneTimePassword
* @return bool
*/
private function validateTwoFactorLogin(Request $request, User $user, string $oneTimePassword): bool
{
//retrieve secret
$secret = $user->google2fa_secret;
$authenticator = app(Authenticator::class)->boot($request);
// try provided token as a 2FA code, or as a recovery code
if ($authenticator->verifyGoogle2FA($secret, $oneTimePassword)
|| $user->recoveryChallenge($oneTimePassword)) {
$authenticator->logout();
return true;
}
return false;
}
/**
* Generate a secret key in Base32 format.
*
* @return string
*/
private function generateSecret()
{
return Google2FA::generateSecretKey(32);
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Models\Contact\ContactFieldType;
use Illuminate\Support\Facades\Validator;
class PersonalizationController extends Controller
{
use JsonRespondController;
/**
* Display the personalization page.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('settings.personalization.index')
->withAccountHasLimitations($accountHasLimitations);
}
/**
* Get all the contact field types.
*/
public function getContactFieldTypes()
{
return auth()->user()->account->contactFieldTypes;
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return string
*/
public function storeContactFieldType(Request $request)
{
Validator::make($request->all(), [
'name' => 'required|max:255',
'icon' => 'max:255|nullable',
'protocol' => 'max:255|nullable',
])->validate();
return auth()->user()->account->contactFieldTypes()->create(
$request->only([
'name',
'protocol',
])
+ [
'fontawesome_icon' => $request->input('icon'),
'account_id' => auth()->user()->account_id,
]
);
}
/**
* Edit a newly created resource in storage.
*
* @param Request $request
* @param ContactFieldType $contactFieldType
* @return ContactFieldType
*/
public function editContactFieldType(Request $request, ContactFieldType $contactFieldType): ContactFieldType
{
Validator::make($request->all(), [
'name' => 'required|max:255',
'icon' => 'max:255|nullable',
'protocol' => 'max:255|nullable',
])->validate();
$contactFieldType->update(
$request->only([
'name',
'protocol',
])
+ [
'fontawesome_icon' => $request->input('icon'),
]
);
return $contactFieldType;
}
/**
* Destroy the contact field type.
*/
public function destroyContactFieldType(Request $request, ContactFieldType $contactFieldType)
{
if (! $contactFieldType->delible) {
return $this->respondUnauthorized();
}
// find all the contact fields that have this contact field types
$contactFields = auth()->user()->account->contactFields
->where('contact_field_type_id', $contactFieldType->id);
foreach ($contactFields as $contactField) {
$contactField->delete();
}
$contactFieldType->delete();
return $this->respondObjectDeleted($contactFieldType->id);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Settings;
use PragmaRX\Random\Random;
use Illuminate\Http\Request;
use App\Models\User\RecoveryCode;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
class RecoveryCodesController extends Controller
{
use JsonRespondController;
/**
* Generate recovery codes.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Support\Collection<array-key, array{id: int, recovery: string, used: bool}>
*/
public function store(Request $request)
{
// Remove previous codes
auth()->user()->recoveryCodes()
->each(function ($code) {
$code->delete();
});
// Generate new codes
$this->generate();
$codes = auth()->user()->recoveryCodes()->get();
return $this->response($codes);
}
/**
* Get list of recovery codes.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Support\Collection<array-key, array{id: int, recovery: string, used: bool}>
*/
public function index(Request $request)
{
$codes = auth()->user()->recoveryCodes()->get();
if (count($codes) == 0) {
$this->generate();
$codes = auth()->user()->recoveryCodes()->get();
}
return $this->response($codes);
}
/**
* Format codes collection for response.
*
* @param \Illuminate\Support\Collection<array-key, \App\Models\User\RecoveryCode> $codes
* @return \Illuminate\Support\Collection<array-key, array{id: int, recovery: string, used: bool}>
*/
private function response($codes)
{
return $codes->map(function (RecoveryCode $code): array {
return [
'id' => $code->id,
'recovery' => $code->recovery,
'used' => (bool) $code->used,
];
});
}
/**
* Generate new recovery codes.
*
* @return void
*/
private function generate()
{
// Generate new codes
$random = new Random();
$random->uppercase(true);
$codes = [];
for ($i = 1; $i <= (int) config('auth.recovery.count'); $i++) {
$blocks = [];
for ($j = 1; $j <= (int) config('auth.recovery.blocks'); $j++) {
$blocks[] = $random->size(config('auth.recovery.chars'))->get();
}
$codes[] = implode('-', $blocks);
}
foreach ($codes as $code) {
RecoveryCode::create([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'recovery' => $code,
]);
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Contact\ReminderRule;
use App\Traits\JsonRespondController;
class ReminderRulesController extends Controller
{
use JsonRespondController;
/**
* Get all the reminder rules.
*/
public function index()
{
$reminderRules = auth()->user()->account->reminderRules;
return $reminderRules->map(function ($reminderRule) {
return $this->format($reminderRule);
});
}
public function toggle(Request $request, ReminderRule $reminderRule)
{
$reminderRule->active = ! $reminderRule->active;
$reminderRule->save();
return $this->respond([
'data' => $this->format($reminderRule),
]);
}
private function format(ReminderRule $reminderRule)
{
return [
'id' => $reminderRule->id,
'number_of_days_before' => $reminderRule->number_of_days_before,
'active' => $reminderRule->active,
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Models\Account\Photo;
use App\Helpers\AccountHelper;
use App\Helpers\StorageHelper;
use App\Models\Contact\Document;
use App\Http\Controllers\Controller;
class StorageController extends Controller
{
/**
* Get all the information about the account in terms of storage.
*/
public function index()
{
$documents = Document::where('account_id', auth()->user()->account_id)->get();
$photos = Photo::where('account_id', auth()->user()->account_id)->get();
/** @var \Illuminate\Support\Collection<array-key, \Illuminate\Database\Eloquent\Model> */
$documents = collect($documents);
$elements = $documents->concat($photos)->sortByDesc('created_at');
// size is in bytes in the database
$currentAccountSize = StorageHelper::getAccountStorageSize(auth()->user()->account);
if ($currentAccountSize != 0) {
$currentAccountSize = round($currentAccountSize / 1000000);
}
// correspondingPercent
$percentUsage = round($currentAccountSize * 100 / config('monica.max_storage_size'));
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('settings.storage.index')
->withAccountHasLimitations($accountHasLimitations)
->withElements($elements)
->withCurrentAccountSize($currentAccountSize)
->withAccountLimit(config('monica.max_storage_size'))
->withPercentUsage($percentUsage);
}
}

View File

@@ -0,0 +1,367 @@
<?php
namespace App\Http\Controllers\Settings;
use Illuminate\View\View;
use App\Traits\StripeCall;
use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use Laravel\Cashier\Cashier;
use Laravel\Cashier\Payment;
use App\Helpers\AccountHelper;
use App\Helpers\InstanceHelper;
use App\Exceptions\StripeException;
use Illuminate\Support\Facades\App;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Contracts\View\Factory;
use Stripe\Exception\ApiErrorException;
use Laravel\Cashier\Exceptions\IncompletePayment;
use App\Services\Account\Settings\ArchiveAllContacts;
class SubscriptionsController extends Controller
{
use StripeCall;
/**
* Display a listing of the resource.
*
* @return View|Factory|RedirectResponse
*/
public function index()
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
$account = auth()->user()->account;
$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && (! $subscription || $subscription->ended())) {
return view('settings.subscriptions.blank', [
'numberOfCustomers' => InstanceHelper::getNumberOfPaidSubscribers(),
]);
}
$hasInvoices = $account->hasStripeId() && $account->hasInvoices();
$invoices = null;
if ($hasInvoices) {
$invoices = $account->invoices();
}
try {
$planInformation = $this->stripeCall(function () use ($subscription): ?array {
return InstanceHelper::getPlanInformationFromSubscription($subscription);
});
} catch (StripeException $e) {
$planInformation = null;
}
return view('settings.subscriptions.account', [
'planInformation' => $planInformation,
'subscription' => $subscription,
'hasInvoices' => $hasInvoices,
'invoices' => $invoices,
'accountHasLimitations' => AccountHelper::hasLimitations($account),
]);
}
/**
* Display the upgrade view page.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function upgrade(Request $request)
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
if (auth()->user()->account->isSubscribed()) {
return redirect()->route('settings.subscriptions.index');
}
$plan = $request->query('plan');
if ($plan !== 'monthly' && $plan !== 'annual') {
abort(404);
}
$planInformation = InstanceHelper::getPlanInformationFromConfig($plan);
if ($planInformation === null) {
abort(404);
}
return view('settings.subscriptions.upgrade', [
'planInformation' => $planInformation,
'nextTheoriticalBillingDate' => DateHelper::getFullDate(DateHelper::getNextTheoriticalBillingDate($plan)),
'intent' => auth()->user()->account->createSetupIntent(),
]);
}
/**
* Display the update view page.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function update(Request $request)
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
$account = auth()->user()->account;
$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && (! $subscription || $subscription->ended())) {
return view('settings.subscriptions.blank', [
'numberOfCustomers' => InstanceHelper::getNumberOfPaidSubscribers(),
]);
}
$planInformation = InstanceHelper::getPlanInformationFromSubscription($subscription);
if ($planInformation === null) {
abort(404);
}
$plans = collect();
foreach (['monthly', 'annual'] as $plan) {
$plans->push(InstanceHelper::getPlanInformationFromConfig($plan));
}
$legacyPlan = null;
if (! $plans->contains(function ($value) use ($planInformation) {
return $value['id'] === $planInformation['id'];
})) {
$legacyPlan = $planInformation;
}
return view('settings.subscriptions.update', [
'planInformation' => $planInformation,
'plans' => $plans,
'legacyPlan' => $legacyPlan,
]);
}
/**
* Process the update process.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function processUpdate(Request $request)
{
$account = auth()->user()->account;
$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && ! $subscription) {
return redirect()->route('settings.index');
}
try {
$account->updateSubscription($request->input('frequency'), $subscription);
} catch (StripeException $e) {
return back()
->withInput()
->withErrors($e->getMessage());
}
return redirect()->route('settings.subscriptions.index');
}
/**
* Display the confirm view page.
*
* @return View|Factory|RedirectResponse
*
* @throws ApiErrorException
*/
public function confirmPayment($id)
{
try {
$payment = $this->stripeCall(function () use ($id): \Stripe\PaymentIntent {
return Cashier::stripe()->paymentIntents->retrieve($id);
});
} catch (StripeException $e) {
return back()->withErrors($e->getMessage());
}
return view('settings.subscriptions.confirm', [
'payment' => new Payment($payment),
'redirect' => request('redirect'),
]);
}
/**
* Display the upgrade success page.
*
* @return View|Factory|RedirectResponse
*/
public function upgradeSuccess()
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
return view('settings.subscriptions.success');
}
/**
* Display the downgrade success page.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function downgradeSuccess(Request $request)
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
return view('settings.subscriptions.downgrade-success');
}
/**
* Display the archive all your contacts page.
*
* @return View|Factory|RedirectResponse
*/
public function archive()
{
return view('settings.subscriptions.archive');
}
/**
* Process the Archive process.
*
* @return RedirectResponse
*/
public function processArchive()
{
app(ArchiveAllContacts::class)->execute([
'account_id' => auth()->user()->account_id,
]);
return redirect()->route('settings.subscriptions.downgrade');
}
/**
* Display the downgrade view page.
*
* @return View|Factory|RedirectResponse
*/
public function downgrade()
{
$account = auth()->user()->account;
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && ! $subscription) {
return redirect()->route('settings.index');
}
return view('settings.subscriptions.downgrade-checklist')
->with('numberOfActiveContacts', $account->allContacts()->active()->count())
->with('numberOfPendingInvitations', $account->invitations()->count())
->with('numberOfUsers', $account->users()->count())
->with('accountHasLimitations', AccountHelper::hasLimitations($account))
->with('hasReachedContactLimit', ! AccountHelper::isBelowContactLimit($account))
->with('canDowngrade', AccountHelper::canDowngrade($account));
}
/**
* Process the downgrade process.
*
* @return RedirectResponse
*/
public function processDowngrade()
{
$account = auth()->user()->account;
if (! AccountHelper::canDowngrade($account)) {
return redirect()->route('settings.subscriptions.downgrade');
}
$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && ! $subscription) {
return redirect()->route('settings.index');
}
try {
$account->subscriptionCancel();
} catch (StripeException $e) {
return back()
->withInput()
->withErrors($e->getMessage());
}
return redirect()->route('settings.subscriptions.downgrade.success');
}
/**
* Process the upgrade payment.
*
* @param Request $request
* @return RedirectResponse
*/
public function processPayment(Request $request)
{
if (! config('monica.requires_subscription')) {
return redirect()->route('settings.index');
}
try {
auth()->user()->account
->subscribe($request->input('payment_method'), $request->input('plan'));
} catch (IncompletePayment $e) {
return redirect()->route(
'settings.subscriptions.confirm',
[$e->payment->asStripePaymentIntent()->id, 'redirect' => route('settings.subscriptions.upgrade.success')]
);
} catch (StripeException $e) {
return back()
->withInput()
->withErrors($e->getMessage());
}
return redirect()->route('settings.subscriptions.upgrade.success');
}
/**
* Download the invoice as PDF.
*
* @param mixed $invoiceId
* @return \Symfony\Component\HttpFoundation\Response
*/
public function downloadInvoice($invoiceId)
{
return auth()->user()->account->downloadInvoice($invoiceId, [
'vendor' => 'Monica',
'product' => trans('settings.subscriptions_pdf_title', ['name' => config('monica.paid_plan_monthly_friendly_name')]),
]);
}
/**
* Download the invoice as PDF.
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|null
*/
public function forceCompletePaymentOnTesting(Request $request): ?RedirectResponse
{
if (App::environment('production')) {
return null;
}
$subscription = auth()->user()->account->getSubscribedPlan();
$subscription->stripe_status = 'active';
$subscription->save();
return redirect()->route('settings.subscriptions.index');
}
}