Changeset View
Changeset View
Standalone View
Standalone View
src/app/Jobs/WalletCheck.php
- This file was added.
<?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'); | |||||
vanmeeuwen: For consistency, should we use `balance_negative_since`? | |||||
machniakAuthorUnsubmitted Not Done Inline ActionsSure. machniak: Sure. | |||||
$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 | |||||
vanmeeuwenUnsubmitted Not Done Inline Actions"comming" -> "upcoming" vanmeeuwen: "comming" -> "upcoming" | |||||
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); | |||||
machniakAuthorUnsubmitted Done Inline Actions@vanmeeuwen what do you think about storing entries like these in transactions table? I mean we'd have "for free" a wallet history including sent mail notifications. Of course, it would appear in UI for admins only. machniak: @vanmeeuwen what do you think about storing entries like these in `transactions` table? I mean… | |||||
vanmeeuwenUnsubmitted Not Done Inline ActionsI would suppose this could get in as a future development, such that it would more generically be a log of "what happened". In this case, we can use system logs and possibly even nginx imap/smtp proxy requests, as well as actions such as these and the actions of users, in a more complete timeline. vanmeeuwen: I would suppose this could get in as a future development, such that it would more generically… | |||||
} | |||||
/** | |||||
* 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 | |||||
} | |||||
} |
For consistency, should we use balance_negative_since?