Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117871051
D1558.1775328406.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None
D1558.1775328406.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D1558: Wallet balance checks/notifications/actions
Attached
Detach File
Event Timeline