Page MenuHomePhorge

D1558.1775328406.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D1558.1775328406.diff

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
@@ -37,7 +37,10 @@
*/
public function handle()
{
- $wallets = \App\Wallet::all();
+ // Get all wallets, excluding deleted accounts
+ $wallets = \App\Wallet::join('users', 'users.id', '=', 'wallets.user_id')
+ ->whereNull('users.deleted_at')
+ ->get();
foreach ($wallets as $wallet) {
$charge = $wallet->chargeEntitlements();
@@ -50,6 +53,11 @@
// Top-up the wallet if auto-payment enabled for the wallet
\App\Jobs\WalletCharge::dispatch($wallet);
}
+
+ if ($wallet->balance < 0) {
+ // Check the account balance, send notifications, suspend, delete
+ \App\Jobs\WalletCheck::dispatch($wallet);
+ }
}
}
}
diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/WalletCheck.php
@@ -0,0 +1,216 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Wallet;
+use Carbon\Carbon;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Mail;
+
+class WalletCheck implements ShouldQueue
+{
+ use Dispatchable;
+ use InteractsWithQueue;
+ use Queueable;
+ use SerializesModels;
+
+ /** @var \App\Wallet A wallet object */
+ protected $wallet;
+
+ /** @var int The number of seconds to wait before retrying the job. */
+ public $retryAfter = 10;
+
+ /** @var int How many times retry the job if it fails. */
+ public $tries = 5;
+
+ /** @var bool Delete the job if the wallet no longer exist. */
+ public $deleteWhenMissingModels = true;
+
+
+ /**
+ * Create a new job instance.
+ *
+ * @param \App\Wallet $wallet The wallet that has been charged.
+ *
+ * @return void
+ */
+ public function __construct(Wallet $wallet)
+ {
+ $this->wallet = $wallet;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ if ($this->wallet->balance >= 0) {
+ return;
+ }
+
+ $negative_since = $this->wallet->getSetting('negative_since');
+ $negative_since = new Carbon($negative_since);
+ $now = Carbon::now();
+
+ $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
+
+ // Delete the account
+ if ($negative_since->copy()->addDays($delete + $suspend + $remind) < $now) {
+ $this->deleteAccount();
+ return;
+ }
+
+ // Warn about comming account deletion
+ if ($negative_since->copy()->addDays($delete + $suspend + $remind - $warn) < $now) {
+ $this->warnBeforeDelete();
+ return;
+ }
+
+ // Suspend the account
+ if ($negative_since->copy()->addDays($suspend + $remind) < $now) {
+ $this->suspendAccount();
+ return;
+ }
+
+ // Send the second reminder
+ if ($negative_since->copy()->addDays($remind) < $now) {
+ $this->secondReminder();
+ return;
+ }
+
+ // Send the initial reminder
+ // Give it an hour so the async recurring payment has a chance to be finished
+ if ($negative_since->copy()->addHours(1) < $now) {
+ $this->initialReminder();
+ return;
+ }
+ }
+
+ /**
+ * Send the initial reminder
+ */
+ protected function initialReminder()
+ {
+ if ($this->wallet->getSetting('balance_warning_initial')) {
+ return;
+ }
+
+ // TODO: Should we check it the account is already suspended?
+
+ $this->sendMail(new \App\Mail\NegativeBalanceInitial($this->wallet));
+
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_initial', $now);
+ }
+
+ /**
+ * Send the second reminder
+ */
+ protected function secondReminder()
+ {
+ if ($this->wallet->getSetting('balance_warning_second')) {
+ return;
+ }
+
+ // TODO: Should we check it the account is already suspended?
+
+ $this->sendMail(new \App\Mail\NegativeBalanceSecond($this->wallet));
+
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_second', $now);
+ }
+
+ /**
+ * Suspend the account (and send the warning)
+ */
+ protected function suspendAccount()
+ {
+ if ($this->wallet->getSetting('balance_warning_suspended')) {
+ return;
+ }
+
+ // Suspend the account
+ foreach ($this->wallet->entitlements as $entitlement) {
+ if (
+ $entitlement->entitleable_type == \App\Domain::class
+ || $entitlement->entitleable_type == \App\User::class
+ ) {
+ $entitlement->entitleable->suspend();
+ }
+ }
+
+ $this->sendMail(new \App\Mail\NegativeBalanceSuspended($this->wallet));
+
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_suspended', $now);
+ }
+
+ /**
+ * Send the last warning before delete
+ */
+ protected function warnBeforeDelete()
+ {
+ if ($this->wallet->getSetting('balance_warning_before_delete')) {
+ return;
+ }
+
+ // Sanity check, already deleted
+ if (!$this->wallet->owner) {
+ return;
+ }
+
+ $this->sendMail(new \App\Mail\NegativeBalanceBeforeDelete($this->wallet), true);
+
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_before_delete', $now);
+ }
+
+ /**
+ * Delete the account
+ */
+ protected function deleteAccount()
+ {
+ // 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.
+ // The dirty work will be done by UserObserver
+ if ($this->wallet->owner) {
+ $this->wallet->owner->delete();
+ }
+ }
+
+ /**
+ * Send the email
+ *
+ * @param \Illuminate\Mail\Mailable $mail The email object
+ * @param bool $with_external Use users's external email
+ */
+ protected function sendMail($mail, $with_external = false): void
+ {
+ $to = $this->wallet->owner->email;
+ $cc = [];
+
+ // TODO: If user has no mailbox entitlement we should not send
+ // the email to his main address, but use external address, if defined
+
+ if ($with_external) {
+ $ext_email = $this->wallet->owner->getSetting('external_email');
+ if ($ext_email && $ext_email != $to) {
+ $cc[] = $ext_email;
+ }
+ }
+
+ Mail::to($to)->cc($cc)->send($mail);
+
+ // TODO: Send the email to all wallet controllers
+ }
+}
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
@@ -69,4 +69,41 @@
return true;
}
+
+ /**
+ * Handle the wallet "updated" event.
+ *
+ * @param \App\Wallet $wallet The wallet.
+ *
+ * @return void
+ */
+ public function updated(Wallet $wallet)
+ {
+ $negative_since = $wallet->getSetting('negative_since');
+
+ if ($wallet->balance < 0) {
+ if (!$negative_since) {
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $wallet->setSetting('negative_since', $now);
+ }
+ } elseif ($negative_since) {
+ $wallet->setSettings([
+ 'negative_since' => null,
+ 'balance_warning_initial' => null,
+ 'balance_warning_second' => null,
+ 'balance_warning_suspended' => null,
+ 'balance_warning_before_delete' => null,
+ ]);
+
+ // Unsuspend the account/domains/users
+ foreach ($wallet->entitlements as $entitlement) {
+ if (
+ $entitlement->entitleable_type == \App\Domain::class
+ || $entitlement->entitleable_type == \App\User::class
+ ) {
+ $entitlement->entitleable->unsuspend();
+ }
+ }
+ }
+ }
}

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 4, 6:46 PM (19 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18820436
Default Alt Text
D1558.1775328406.diff (8 KB)

Event Timeline