diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -760,6 +760,7 @@ { $firstName = $user->getSetting('first_name'); $lastName = $user->getSetting('last_name'); + $isDegraded = $user->wallet()->owner->isDegraded(); $cn = "unknown"; $displayname = ""; @@ -820,13 +821,19 @@ $entry['nsroledn'][] = "cn=2fa-user,{$hostedRootDN}"; } - if (in_array("activesync", $roles)) { + if (!$isDegraded && in_array("activesync", $roles)) { $entry['nsroledn'][] = "cn=activesync-user,{$hostedRootDN}"; } - if (!in_array("groupware", $roles)) { + if (!$isDegraded && !in_array("groupware", $roles)) { $entry['nsroledn'][] = "cn=imap-user,{$hostedRootDN}"; } + + if ($isDegraded) { + // FIXME: Should we use a role instead? + $entry['inetuserstatus'] = $user->status | User::STATUS_DEGRADED; + $entry['mailquota'] = 2 * 1048576; + } } /** diff --git a/src/app/Console/Commands/UserDegrade.php b/src/app/Console/Commands/UserDegrade.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UserDegrade.php @@ -0,0 +1,48 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $user->degrade(); + } +} diff --git a/src/app/Console/Commands/UserUndegrade.php b/src/app/Console/Commands/UserUndegrade.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/UserUndegrade.php @@ -0,0 +1,48 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $user->undegrade(); + } +} diff --git a/src/app/Console/Commands/WalletCharge.php b/src/app/Console/Commands/WalletCharge.php --- a/src/app/Console/Commands/WalletCharge.php +++ b/src/app/Console/Commands/WalletCharge.php @@ -68,7 +68,9 @@ } if ($wallet->balance < 0) { - // Check the account balance, send notifications, suspend, delete + // Check the account balance, send notifications, (suspend, delete,) degrade + // TODO: For performance reasons we should probably skip the job + // if the wallet owner is degraded already \App\Jobs\WalletCheck::dispatch($wallet); } } diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php --- a/src/app/Http/Controllers/API/V4/OpenViduController.php +++ b/src/app/Http/Controllers/API/V4/OpenViduController.php @@ -213,7 +213,7 @@ $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted - if (!$room || !$room->owner) { + if (!$room || !$room->owner || $room->owner->isDegraded(true)) { return $this->errorResponse(404, \trans('meet.room-not-found')); } @@ -349,7 +349,7 @@ $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted - if (!$room || !$room->owner) { + if (!$room || !$room->owner || $room->owner->isDegraded(true)) { return $this->errorResponse(404); } diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php --- a/src/app/Http/Controllers/API/V4/UsersController.php +++ b/src/app/Http/Controllers/API/V4/UsersController.php @@ -525,6 +525,8 @@ 'isSuspended' => $user->isSuspended(), 'isActive' => $user->isActive(), 'isDeleted' => $user->isDeleted() || $user->trashed(), + 'isDegraded' => $user->isDegraded(), + 'isAccountDegraded' => $user->isDegraded(true), ]; } diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php --- a/src/app/Jobs/WalletCheck.php +++ b/src/app/Jobs/WalletCheck.php @@ -19,6 +19,8 @@ use Queueable; use SerializesModels; + public const THRESHOLD_DEGRADE = 'degrade'; + public const THRESHOLD_BEFORE_DEGRADE = 'before_degrade'; public const THRESHOLD_DELETE = 'delete'; public const THRESHOLD_BEFORE_DELETE = 'before_delete'; public const THRESHOLD_SUSPEND = 'suspend'; @@ -64,62 +66,80 @@ } $now = Carbon::now(); - - // Delete the account - if (self::threshold($this->wallet, self::THRESHOLD_DELETE) < $now) { - $this->deleteAccount(); - return self::THRESHOLD_DELETE; - } - - // Warn about the upcomming account deletion - if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_DELETE) < $now) { - $this->warnBeforeDelete(); - return self::THRESHOLD_BEFORE_DELETE; +/* + // Steps for old "first suspend then delete" approach + $steps = [ + // Send the initial reminder + self::THRESHOLD_INITIAL => 'initialReminder', + // Try to top-up the wallet before the second reminder + self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet', + // Send the second reminder + self::THRESHOLD_REMINDER => 'secondReminder', + // Try to top-up the wallet before suspending the account + self::THRESHOLD_BEFORE_SUSPEND => 'topUpWallet', + // Suspend the account + self::THRESHOLD_SUSPEND => 'suspendAccount', + // Warn about the upcomming account deletion + self::THRESHOLD_BEFORE_DELETE => 'warnBeforeDelete', + // Delete the account + self::THRESHOLD_DELETE => 'deleteAccount', + ]; +*/ + // Steps for "demote instead of suspend+delete" approach + $steps = [ + // Send the initial reminder + self::THRESHOLD_INITIAL => 'initialReminderForDegrade', + // Try to top-up the wallet before the second reminder + self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet', + // Send the second reminder + self::THRESHOLD_REMINDER => 'secondReminderForDegrade', + // Try to top-up the wallet before the account degradation + self::THRESHOLD_BEFORE_DEGRADE => 'topUpWallet', + // Degrade the account + self::THRESHOLD_DEGRADE => 'degradeAccount', + ]; + + foreach (array_reverse($steps, true) as $type => $method) { + if (self::threshold($this->wallet, $type) < $now) { + $this->{$method}(); + return $type; + } } - // Suspend the account - if (self::threshold($this->wallet, self::THRESHOLD_SUSPEND) < $now) { - $this->suspendAccount(); - return self::THRESHOLD_SUSPEND; - } + return null; + } - // Try to top-up the wallet before suspending the account - if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_SUSPEND) < $now) { - PaymentsController::topUpWallet($this->wallet); - return self::THRESHOLD_BEFORE_SUSPEND; + /** + * Send the initial reminder (for the suspend+delete process) + */ + protected function initialReminder() + { + if ($this->wallet->getSetting('balance_warning_initial')) { + return; } - // Send the second reminder - if (self::threshold($this->wallet, self::THRESHOLD_REMINDER) < $now) { - $this->secondReminder(); - return self::THRESHOLD_REMINDER; - } + // TODO: Should we check if the account is already suspended? - // Try to top-up the wallet before the second reminder - if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_REMINDER) < $now) { - PaymentsController::topUpWallet($this->wallet); - return self::THRESHOLD_BEFORE_REMINDER; - } + $label = "Notification sent for"; - // Send the initial reminder - if (self::threshold($this->wallet, self::THRESHOLD_INITIAL) < $now) { - $this->initialReminder(); - return self::THRESHOLD_INITIAL; - } + $this->sendMail(\App\Mail\NegativeBalance::class, false, $label); - return null; + $now = \Carbon\Carbon::now()->toDateTimeString(); + $this->wallet->setSetting('balance_warning_initial', $now); } /** - * Send the initial reminder + * Send the initial reminder (for the process of degrading a account) */ - protected function initialReminder() + protected function initialReminderForDegrade() { if ($this->wallet->getSetting('balance_warning_initial')) { return; } - // TODO: Should we check if the account is already suspended? + if ($this->wallet->owner && $this->wallet->owner->isDegraded()) { + return; + } $label = "Notification sent for"; @@ -130,7 +150,7 @@ } /** - * Send the second reminder + * Send the second reminder (for the suspend+delete process) */ protected function secondReminder() { @@ -148,6 +168,27 @@ $this->wallet->setSetting('balance_warning_reminder', $now); } + /** + * Send the second reminder (for the process of degrading a account) + */ + protected function secondReminderForDegrade() + { + if ($this->wallet->getSetting('balance_warning_reminder')) { + return; + } + + if ($this->wallet->owner && $this->wallet->owner->isDegraded()) { + return; + } + + $label = "Reminder sent for"; + + $this->sendMail(\App\Mail\NegativeBalanceReminderDegrade::class, true, $label); + + $now = \Carbon\Carbon::now()->toDateTimeString(); + $this->wallet->setSetting('balance_warning_reminder', $now); + } + /** * Suspend the account (and send the warning) */ @@ -203,6 +244,36 @@ $this->wallet->setSetting('balance_warning_before_delete', $now); } + /** + * Degrade the account + */ + protected function degradeAccount() + { + // TODO: This will not work when we actually allow multiple-wallets per account + // but in this case we anyway have to change the whole thing + // and calculate summarized balance from all wallets. + if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { + return; + } + + $email = $this->wallet->owner->email; + + // The dirty work will be done by UserObserver + $this->wallet->owner->degrade(); + + \Log::info( + sprintf( + "[WalletCheck] Account degraded %s (%s)", + $this->wallet->id, + $email + ) + ); + + $label = "Degradation notification sent for"; + + $this->sendMail(\App\Mail\NegativeBalanceDegraded::class, true, $label); + } + /** * Delete the account */ @@ -292,47 +363,50 @@ $negative_since = new Carbon($negative_since); } - $remind = 7; // remind after first X days - $suspend = 14; // suspend after next X days - $delete = 21; // delete after next X days - $warn = 3; // warn about delete on X days before delete - - // Acount deletion - if ($type == self::THRESHOLD_DELETE) { - return $negative_since->addDays($delete + $suspend + $remind); - } - - // Warning about the upcomming account deletion - if ($type == self::THRESHOLD_BEFORE_DELETE) { - return $negative_since->addDays($delete + $suspend + $remind - $warn); - } - - // Account suspension - if ($type == self::THRESHOLD_SUSPEND) { - return $negative_since->addDays($suspend + $remind); - } - - // A day before account suspension - if ($type == self::THRESHOLD_BEFORE_SUSPEND) { - return $negative_since->addDays($suspend + $remind - 1); - } - - // Second notification - if ($type == self::THRESHOLD_REMINDER) { - return $negative_since->addDays($remind); - } - - // A day before the second reminder - if ($type == self::THRESHOLD_BEFORE_REMINDER) { - return $negative_since->addDays($remind - 1); - } - // Initial notification // Give it an hour so the async recurring payment has a chance to be finished if ($type == self::THRESHOLD_INITIAL) { return $negative_since->addHours(1); } +/* + $thresholds = [ + // A day before the second reminder + self::THRESHOLD_BEFORE_REMINDER => 7 - 1, + // Second notification + self::THRESHOLD_REMINDER => 7, + // A day before account suspension + self::THRESHOLD_BEFORE_SUSPEND => 14 + 7 - 1, + // Account suspension + self::THRESHOLD_SUSPEND => 14 + 7, + // Warning about the upcomming account deletion + self::THRESHOLD_BEFORE_DELETE => 21 + 14 + 7 - 3, + // Acount deletion + self::THRESHOLD_DELETE => 21 + 14 + 7, + ]; +*/ + $thresholds = [ + // Top-up a walet before the second notification + self::THRESHOLD_BEFORE_REMINDER => 7 - 1, + // Second notification + self::THRESHOLD_REMINDER => 7, + // Last chance to top-up the wallet + self::THRESHOLD_BEFORE_DEGRADE => 13, + // Account degradation + self::THRESHOLD_DEGRADE => 14, + ]; + + if (!empty($thresholds[$type])) { + return $negative_since->addDays($thresholds[$type]); + } return null; } + + /** + * Try to automatically top-up the wallet + */ + protected function topUpWallet(): void + { + PaymentsController::topUpWallet($this->wallet); + } } diff --git a/src/app/Mail/NegativeBalanceDegraded.php b/src/app/Mail/NegativeBalanceDegraded.php new file mode 100644 --- /dev/null +++ b/src/app/Mail/NegativeBalanceDegraded.php @@ -0,0 +1,78 @@ +wallet = $wallet; + $this->user = $user; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $subject = \trans('mail.negativebalancedegraded-subject', ['site' => \config('app.name')]); + + $this->view('emails.html.negative_balance_degraded') + ->text('emails.plain.negative_balance_degraded') + ->subject($subject) + ->with([ + 'site' => \config('app.name'), + 'subject' => $subject, + 'username' => $this->user->name(true), + 'supportUrl' => \config('app.support_url'), + 'walletUrl' => Utils::serviceUrl('/wallet'), + ]); + + return $this; + } + + /** + * Render the mail template with fake data + * + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output + */ + public static function fakeRender(string $type = 'html'): string + { + $wallet = new Wallet(); + $user = new User(); + + $mail = new self($wallet, $user); + + return Helper::render($mail, $type); + } +} diff --git a/src/app/Mail/NegativeBalanceReminderDegrade.php b/src/app/Mail/NegativeBalanceReminderDegrade.php new file mode 100644 --- /dev/null +++ b/src/app/Mail/NegativeBalanceReminderDegrade.php @@ -0,0 +1,81 @@ +wallet = $wallet; + $this->user = $user; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $threshold = WalletCheck::threshold($this->wallet, WalletCheck::THRESHOLD_DEGRADE); + + $subject = \trans('mail.negativebalancereminder-subject', ['site' => \config('app.name')]); + + $this->view('emails.html.negative_balance_reminder') + ->text('emails.plain.negative_balance_reminder') + ->subject($subject) + ->with([ + 'site' => \config('app.name'), + 'subject' => $subject, + 'username' => $this->user->name(true), + 'supportUrl' => \config('app.support_url'), + 'walletUrl' => Utils::serviceUrl('/wallet'), + 'date' => $threshold->toDateString(), + ]); + + return $this; + } + + /** + * Render the mail template with fake data + * + * @param string $type Output format ('html' or 'text') + * + * @return string HTML or Plain Text output + */ + public static function fakeRender(string $type = 'html'): string + { + $wallet = new Wallet(); + $user = new User(); + + $mail = new self($wallet, $user); + + return Helper::render($mail, $type); + } +} diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php --- a/src/app/Observers/EntitlementObserver.php +++ b/src/app/Observers/EntitlementObserver.php @@ -116,6 +116,10 @@ $owner = $entitlement->wallet->owner; + if ($owner->isDegraded()) { + return; + } + // Determine if we're still within the free first month $freeMonthEnds = $owner->created_at->copy()->addMonthsWithoutOverflow(1); diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php --- a/src/app/Observers/UserObserver.php +++ b/src/app/Observers/UserObserver.php @@ -349,14 +349,43 @@ } /** - * Handle the "updating" event. + * Handle the "updated" event. * * @param User $user The user that is being updated. * * @return void */ - public function updating(User $user) + public function updated(User $user) { \App\Jobs\User\UpdateJob::dispatch($user->id); + + $oldStatus = $user->getOriginal('status'); + $newStatus = $user->status; + + if (($oldStatus & User::STATUS_DEGRADED) !== ($newStatus & User::STATUS_DEGRADED)) { + $wallets = []; + + // Charge all entitlements as if they were being deleted, + // but don't delete them. Just debit the wallet and update + // entitlements' updated_at timestamp. On un-degrade we still + // update updated_at, but with no debit (the cost is 0 on a degraded account). + foreach ($user->wallets as $wallet) { + $wallet->updateEntitlements($user->isDegraded()); + $wallets[] = $wallet->id; + } + + // (Un-)degrade users by invoking an update job. + // LDAP backend will read the wallet owner's degraded status and + // set LDAP attributes accordingly. + // We do not change their status as their wallets have its own state + \App\Entitlement::whereIn('wallet_id', $wallets) + ->where('entitleable_id', '!=', $user->id) + ->where('entitleable_type', User::class) + ->pluck('entitleable_id') + ->unique() + ->each(function ($user_id) { + \App\Jobs\User\UpdateJob::dispatch($user_id); + }); + } } } diff --git a/src/app/Observers/WalletObserver.php b/src/app/Observers/WalletObserver.php --- a/src/app/Observers/WalletObserver.php +++ b/src/app/Observers/WalletObserver.php @@ -95,10 +95,12 @@ 'balance_warning_before_delete' => null, ]); - // Unsuspend the account/domains/users + // Un-suspend and un-degrade the account/domains/users if ($wallet->owner) { $wallet->owner->unsuspend(); + $wallet->owner->undegrade(); } + foreach ($wallet->entitlements as $entitlement) { if ( $entitlement->entitleable_type == \App\Domain::class diff --git a/src/app/User.php b/src/app/User.php --- a/src/app/User.php +++ b/src/app/User.php @@ -42,6 +42,8 @@ public const STATUS_LDAP_READY = 1 << 4; // user mailbox has been created in IMAP public const STATUS_IMAP_READY = 1 << 5; + // user in "limited feature-set" state + public const STATUS_DEGRADED = 1 << 6; // change the default primary key type @@ -262,6 +264,21 @@ return $this->canDelete($object); } + /** + * Degrade the user + * + * @return void + */ + public function degrade(): void + { + if ($this->isDegraded()) { + return; + } + + $this->status |= User::STATUS_DEGRADED; + $this->save(); + } + /** * Return the \App\Domain for this user. * @@ -449,7 +466,7 @@ } /** - * Returns whether this domain is active. + * Returns whether this user is active. * * @return bool */ @@ -459,7 +476,28 @@ } /** - * Returns whether this domain is deleted. + * Returns whether this user (or its wallet owner) is degraded. + * + * @param bool $owner Check also the wallet owner instead just the user himself + * + * @return bool + */ + public function isDegraded(bool $owner = false): bool + { + if ($this->status & self::STATUS_DEGRADED) { + return true; + } + + if ($owner) { + $owner = $this->wallet()->owner; + return $owner && $owner->isDegraded(); + } + + return false; + } + + /** + * Returns whether this user is deleted. * * @return bool */ @@ -469,8 +507,7 @@ } /** - * Returns whether this (external) domain has been verified - * to exist in DNS. + * Returns whether this user is registered in IMAP. * * @return bool */ @@ -500,7 +537,7 @@ } /** - * Returns whether this domain is suspended. + * Returns whether this user is suspended. * * @return bool */ @@ -574,7 +611,7 @@ } /** - * Suspend this domain. + * Suspend this user. * * @return void */ @@ -589,7 +626,22 @@ } /** - * Unsuspend this domain. + * Un-degrade this user. + * + * @return void + */ + public function undegrade(): void + { + if (!$this->isDegraded()) { + return; + } + + $this->status ^= User::STATUS_DEGRADED; + $this->save(); + } + + /** + * Unsuspend this user. * * @return void */ @@ -705,6 +757,7 @@ self::STATUS_DELETED, self::STATUS_LDAP_READY, self::STATUS_IMAP_READY, + self::STATUS_DEGRADED, ]; foreach ($allowed_values as $value) { diff --git a/src/app/Wallet.php b/src/app/Wallet.php --- a/src/app/Wallet.php +++ b/src/app/Wallet.php @@ -58,7 +58,14 @@ } } - public function chargeEntitlements($apply = true) + /** + * Charge entitlements in the wallet + * + * @param bool $apply Set to false for a dry-run mode + * + * @return int Charged amount in cents + */ + public function chargeEntitlements($apply = true): int { // This wallet has been created less than a month ago, this is the trial period if ($this->owner->created_at >= Carbon::now()->subMonthsWithoutOverflow(1)) { @@ -78,8 +85,11 @@ $charges = 0; $discount = $this->getDiscountRate(); + $isDegraded = $this->owner->isDegraded(); - DB::beginTransaction(); + if ($apply) { + DB::beginTransaction(); + } // used to parent individual entitlement billings to the wallet debit. $entitlementTransactions = []; @@ -102,6 +112,10 @@ $cost = (int) ($entitlement->cost * $discount * $diff); + if ($isDegraded) { + $cost = 0; + } + $charges += $cost; // if we're in dry-run, you know... @@ -127,10 +141,9 @@ if ($apply) { $this->debit($charges, $entitlementTransactions); + DB::commit(); } - DB::commit(); - return $charges; } @@ -397,4 +410,72 @@ ] ); } + + /** + * Force-update entitlements' updated_at, charge if needed. + * + * @param bool $withCost When enabled the cost will be charged + * + * @return int Charged amount in cents + */ + public function updateEntitlements($withCost = true): int + { + $charges = 0; + $discount = $this->getDiscountRate(); + $now = Carbon::now(); + + DB::beginTransaction(); + + // used to parent individual entitlement billings to the wallet debit. + $entitlementTransactions = []; + + foreach ($this->entitlements()->get()->fresh() as $entitlement) { + $cost = 0; + $diffInDays = $entitlement->updated_at->diffInDays($now); + + // This entitlement has been created less than or equal to 14 days ago (this is at + // maximum the fourteenth 24-hour period). + if ($entitlement->created_at > Carbon::now()->subDays(14)) { + // $cost=0 + } elseif ($withCost && $diffInDays > 0) { + // The price per day is based on the number of days in the last month + // or the current month if the period does not overlap with the previous month + // FIXME: This really should be simplified to constant $daysInMonth=30 + if ($now->day >= $diffInDays && $now->month == $entitlement->updated_at->month) { + $daysInMonth = $now->daysInMonth; + } else { + $daysInMonth = \App\Utils::daysInLastMonth(); + } + + $pricePerDay = $entitlement->cost / $daysInMonth; + + $cost = (int) (round($pricePerDay * $discount * $diffInDays, 0)); + } + + if ($diffInDays > 0) { + $entitlement->updated_at->setDateFrom($now); + $entitlement->save(); + } + + if ($cost == 0) { + continue; + } + + $charges += $cost; + + // FIXME: Shouldn't we store also cost=0 transactions (to have the full history)? + $entitlementTransactions[] = $entitlement->createTransaction( + \App\Transaction::ENTITLEMENT_BILLED, + $cost + ); + } + + if ($charges > 0) { + $this->debit($charges, $entitlementTransactions); + } + + DB::commit(); + + return $charges; + } } diff --git a/src/resources/js/app.js b/src/resources/js/app.js --- a/src/resources/js/app.js +++ b/src/resources/js/app.js @@ -300,6 +300,9 @@ return 'Active' }, + isDegraded() { + return store.state.authInfo.isAccountDegraded + }, pageName(path) { let page = this.$route.path @@ -337,7 +340,7 @@ return 'text-muted' } - if (user.isSuspended) { + if (user.isDegraded || user.isSuspended) { return 'text-warning' } @@ -352,6 +355,10 @@ return 'Deleted' } + if (user.isDegraded) { + return 'Degraded' + } + if (user.isSuspended) { return 'Suspended' } diff --git a/src/resources/lang/en/mail.php b/src/resources/lang/en/mail.php --- a/src/resources/lang/en/mail.php +++ b/src/resources/lang/en/mail.php @@ -29,6 +29,14 @@ 'negativebalancereminder-body-warning' => "Please, be aware that your account will be suspended " . "if your account balance is not settled by :date.", + 'negativebalancereminderdegrade-body-warning' => "Please, be aware that your account will be degraded " + . "if your account balance is not settled by :date.", + + 'negativebalancedegraded-subject' => ":site Account Degraded", + 'negativebalancedegraded-body' => "Your :site account has been degraded for having a negative balance for too long. " + . "Consider setting up an automatic payment to avoid messages like this in the future.", + 'negativebalancedegraded-body-ext' => "Settle up now to undegrade your account:", + 'negativebalancesuspended-subject' => ":site Account Suspended", 'negativebalancesuspended-body' => "Your :site account has been suspended for having a negative balance for too long. " . "Consider setting up an automatic payment to avoid messages like this in the future.", diff --git a/src/resources/views/emails/html/negative_balance_degraded.blade.php b/src/resources/views/emails/html/negative_balance_degraded.blade.php new file mode 100644 --- /dev/null +++ b/src/resources/views/emails/html/negative_balance_degraded.blade.php @@ -0,0 +1,21 @@ + + + + + + +

{{ __('mail.header', ['name' => $username]) }}

+ +

{{ __('mail.negativebalancedegraded-body', ['site' => $site]) }}

+

{{ __('mail.negativebalancedegraded-body-ext', ['site' => $site]) }}

+

{{ $walletUrl }}

+ +@if ($supportUrl) +

{{ __('mail.support', ['site' => $site]) }}

+

{{ $supportUrl }}

+@endif + +

{{ __('mail.footer1') }}

+

{{ __('mail.footer2', ['site' => $site]) }}

+ + diff --git a/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php b/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php new file mode 100644 --- /dev/null +++ b/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php @@ -0,0 +1,22 @@ + + + + + + +

{{ __('mail.header', ['name' => $username]) }}

+ +

{{ __('mail.negativebalancereminder-body', ['site' => $site]) }}

+

{{ __('mail.negativebalancereminder-body-ext', ['site' => $site]) }}

+

{{ $walletUrl }}

+

{{ __('mail.negativebalancereminderdegrade-body-warning', ['site' => $site, 'date' => $date]) }}

+ +@if ($supportUrl) +

{{ __('mail.support', ['site' => $site]) }}

+

{{ $supportUrl }}

+@endif + +

{{ __('mail.footer1') }}

+

{{ __('mail.footer2', ['site' => $site]) }}

+ + diff --git a/src/resources/views/emails/plain/negative_balance_degraded.blade.php b/src/resources/views/emails/plain/negative_balance_degraded.blade.php new file mode 100644 --- /dev/null +++ b/src/resources/views/emails/plain/negative_balance_degraded.blade.php @@ -0,0 +1,17 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.negativebalancedegraded-body', ['site' => $site]) !!} + +{!! __('mail.negativebalancedegraded-body-ext', ['site' => $site]) !!} + +{!! $walletUrl !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer1') !!} +{!! __('mail.footer2', ['site' => $site]) !!} diff --git a/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php b/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php new file mode 100644 --- /dev/null +++ b/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php @@ -0,0 +1,19 @@ +{!! __('mail.header', ['name' => $username]) !!} + +{!! __('mail.negativebalancereminder-body', ['site' => $site]) !!} + +{!! __('mail.negativebalancereminder-body-ext', ['site' => $site]) !!} + +{!! $walletUrl !!} + +{!! __('mail.negativebalancereminderdegrade-body-warning', ['site' => $site, 'date' => $date]) !!} + +@if ($supportUrl) +{!! __('mail.support', ['site' => $site]) !!} + +{!! $supportUrl !!} +@endif + +-- +{!! __('mail.footer1') !!} +{!! __('mail.footer2', ['site' => $site]) !!} diff --git a/src/resources/vue/Dashboard.vue b/src/resources/vue/Dashboard.vue --- a/src/resources/vue/Dashboard.vue +++ b/src/resources/vue/Dashboard.vue @@ -2,6 +2,12 @@
+
+

+ The account is degraded. Some features has been disabled. Please, make a payment. +

+
+
Your profile @@ -16,7 +22,7 @@ Wallet {{ $root.price(balance) }} - + Video chat beta diff --git a/src/resources/vue/Rooms.vue b/src/resources/vue/Rooms.vue --- a/src/resources/vue/Rooms.vue +++ b/src/resources/vue/Rooms.vue @@ -81,7 +81,7 @@ } }, mounted() { - if (!this.$root.hasSKU('meet')) { + if (!this.$root.hasSKU('meet') || this.$root.isDegraded()) { this.$root.errorPage(403) return } diff --git a/src/resources/vue/User/List.vue b/src/resources/vue/User/List.vue --- a/src/resources/vue/User/List.vue +++ b/src/resources/vue/User/List.vue @@ -4,7 +4,7 @@
User Accounts - + Create user