Changeset View
Changeset View
Standalone View
Standalone View
src/app/Jobs/WalletCheck.php
Show All 14 Lines | |||||
{ | { | ||||
use Dispatchable; | use Dispatchable; | ||||
use InteractsWithQueue; | use InteractsWithQueue; | ||||
use Queueable; | use Queueable; | ||||
use SerializesModels; | use SerializesModels; | ||||
public const THRESHOLD_DEGRADE = 'degrade'; | public const THRESHOLD_DEGRADE = 'degrade'; | ||||
public const THRESHOLD_DEGRADE_REMINDER = 'degrade-reminder'; | public const THRESHOLD_DEGRADE_REMINDER = 'degrade-reminder'; | ||||
public const THRESHOLD_BEFORE_DEGRADE = 'before_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'; | |||||
public const THRESHOLD_BEFORE_SUSPEND = 'before_suspend'; | |||||
public const THRESHOLD_REMINDER = 'reminder'; | public const THRESHOLD_REMINDER = 'reminder'; | ||||
public const THRESHOLD_BEFORE_REMINDER = 'before_reminder'; | public const THRESHOLD_BEFORE_REMINDER = 'before-reminder'; | ||||
public const THRESHOLD_INITIAL = 'initial'; | public const THRESHOLD_INITIAL = 'initial'; | ||||
/** @var int The number of seconds to wait before retrying the job. */ | /** @var int The number of seconds to wait before retrying the job. */ | ||||
public $backoff = 10; | public $backoff = 10; | ||||
/** @var int How many times retry the job if it fails. */ | /** @var int How many times retry the job if it fails. */ | ||||
public $tries = 5; | public $tries = 5; | ||||
Show All 23 Lines | class WalletCheck implements ShouldQueue | ||||
*/ | */ | ||||
public function handle() | public function handle() | ||||
{ | { | ||||
if ($this->wallet->balance >= 0) { | if ($this->wallet->balance >= 0) { | ||||
return null; | return null; | ||||
} | } | ||||
$now = Carbon::now(); | $now = Carbon::now(); | ||||
/* | |||||
// 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 = [ | $steps = [ | ||||
// Send the initial reminder | // Send the initial reminder | ||||
self::THRESHOLD_INITIAL => 'initialReminderForDegrade', | self::THRESHOLD_INITIAL => 'initialReminderForDegrade', | ||||
// Try to top-up the wallet before the second reminder | // Try to top-up the wallet before the second reminder | ||||
self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet', | self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet', | ||||
// Send the second reminder | // Send the second reminder | ||||
self::THRESHOLD_REMINDER => 'secondReminderForDegrade', | self::THRESHOLD_REMINDER => 'secondReminderForDegrade', | ||||
// Try to top-up the wallet before the account degradation | // Try to top-up the wallet before the account degradation | ||||
Show All 13 Lines | public function handle() | ||||
return $type; | return $type; | ||||
} | } | ||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
/** | /** | ||||
* Send the initial reminder (for the suspend+delete process) | |||||
*/ | |||||
protected function initialReminder() | |||||
{ | |||||
if ($this->wallet->getSetting('balance_warning_initial')) { | |||||
return; | |||||
} | |||||
// TODO: Should we check if the account is already suspended? | |||||
$this->sendMail(\App\Mail\NegativeBalance::class, false); | |||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | |||||
$this->wallet->setSetting('balance_warning_initial', $now); | |||||
} | |||||
/** | |||||
* Send the initial reminder (for the process of degrading a account) | * Send the initial reminder (for the process of degrading a account) | ||||
*/ | */ | ||||
protected function initialReminderForDegrade() | protected function initialReminderForDegrade() | ||||
{ | { | ||||
if ($this->wallet->getSetting('balance_warning_initial')) { | if ($this->wallet->getSetting('balance_warning_initial')) { | ||||
return; | return; | ||||
} | } | ||||
if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { | if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { | ||||
return; | return; | ||||
} | } | ||||
if (!$this->wallet->owner->isSuspended()) { | |||||
$this->sendMail(\App\Mail\NegativeBalance::class, false); | $this->sendMail(\App\Mail\NegativeBalance::class, false); | ||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | |||||
$this->wallet->setSetting('balance_warning_initial', $now); | |||||
} | } | ||||
/** | |||||
* Send the second reminder (for the suspend+delete process) | |||||
*/ | |||||
protected function secondReminder() | |||||
{ | |||||
if ($this->wallet->getSetting('balance_warning_reminder')) { | |||||
return; | |||||
} | |||||
// TODO: Should we check if the account is already suspended? | |||||
$this->sendMail(\App\Mail\NegativeBalanceReminder::class, false); | |||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | $now = \Carbon\Carbon::now()->toDateTimeString(); | ||||
$this->wallet->setSetting('balance_warning_reminder', $now); | $this->wallet->setSetting('balance_warning_initial', $now); | ||||
} | } | ||||
/** | /** | ||||
* Send the second reminder (for the process of degrading a account) | * Send the second reminder (for the process of degrading a account) | ||||
*/ | */ | ||||
protected function secondReminderForDegrade() | protected function secondReminderForDegrade() | ||||
{ | { | ||||
if ($this->wallet->getSetting('balance_warning_reminder')) { | if ($this->wallet->getSetting('balance_warning_reminder')) { | ||||
return; | return; | ||||
} | } | ||||
if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { | if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { | ||||
return; | return; | ||||
} | } | ||||
if (!$this->wallet->owner->isSuspended()) { | |||||
$this->sendMail(\App\Mail\NegativeBalanceReminderDegrade::class, true); | $this->sendMail(\App\Mail\NegativeBalanceReminderDegrade::class, true); | ||||
} | |||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | $now = \Carbon\Carbon::now()->toDateTimeString(); | ||||
$this->wallet->setSetting('balance_warning_reminder', $now); | $this->wallet->setSetting('balance_warning_reminder', $now); | ||||
} | } | ||||
/** | /** | ||||
* Suspend the account (and send the warning) | * Degrade the account | ||||
*/ | */ | ||||
protected function suspendAccount() | protected function degradeAccount() | ||||
{ | { | ||||
if ($this->wallet->getSetting('balance_warning_suspended')) { | // The account may be already deleted, or degraded | ||||
return; | if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) { | ||||
} | |||||
// Sanity check, already deleted | |||||
if (!$this->wallet->owner) { | |||||
return; | return; | ||||
} | } | ||||
// Suspend the account | $email = $this->wallet->owner->email; | ||||
$this->wallet->owner->suspendAccount(); | |||||
$this->sendMail(\App\Mail\NegativeBalanceSuspended::class, true); | |||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | // The dirty work will be done by UserObserver | ||||
$this->wallet->setSetting('balance_warning_suspended', $now); | $this->wallet->owner->degrade(); | ||||
} | |||||
/** | \Log::info( | ||||
* Send the last warning before delete | sprintf( | ||||
*/ | "[WalletCheck] Account degraded %s (%s)", | ||||
protected function warnBeforeDelete() | $this->wallet->id, | ||||
{ | |||||
if ($this->wallet->getSetting('balance_warning_before_delete')) { | ) | ||||
return; | ); | ||||
} | |||||
// Sanity check, already deleted | if (!$this->wallet->owner->isSuspended()) { | ||||
if (!$this->wallet->owner) { | $this->sendMail(\App\Mail\NegativeBalanceDegraded::class, true); | ||||
return; | |||||
} | } | ||||
$this->sendMail(\App\Mail\NegativeBalanceBeforeDelete::class, true); | |||||
$now = \Carbon\Carbon::now()->toDateTimeString(); | |||||
$this->wallet->setSetting('balance_warning_before_delete', $now); | |||||
} | } | ||||
/** | /** | ||||
* Send the periodic reminder to the degraded account owners | * Send the periodic reminder to the degraded account owners | ||||
*/ | */ | ||||
protected function degradedReminder() | protected function degradedReminder() | ||||
{ | { | ||||
// Sanity check | // Sanity check | ||||
if (!$this->wallet->owner || !$this->wallet->owner->isDegraded()) { | if (!$this->wallet->owner || !$this->wallet->owner->isDegraded()) { | ||||
return; | return; | ||||
} | } | ||||
if ($this->wallet->owner->isSuspended()) { | |||||
return; | |||||
} | |||||
$now = \Carbon\Carbon::now(); | $now = \Carbon\Carbon::now(); | ||||
$last = $this->wallet->getSetting('degraded_last_reminder'); | $last = $this->wallet->getSetting('degraded_last_reminder'); | ||||
if ($last) { | if ($last) { | ||||
$last = new Carbon($last); | $last = new Carbon($last); | ||||
$period = 14; | $period = 14; | ||||
if ($last->addDays($period) > $now) { | if ($last->addDays($period) > $now) { | ||||
return; | return; | ||||
} | } | ||||
$this->sendMail(\App\Mail\DegradedAccountReminder::class, true); | $this->sendMail(\App\Mail\DegradedAccountReminder::class, false); | ||||
} | } | ||||
$this->wallet->setSetting('degraded_last_reminder', $now->toDateTimeString()); | $this->wallet->setSetting('degraded_last_reminder', $now->toDateTimeString()); | ||||
} | } | ||||
/** | /** | ||||
* Degrade the account | |||||
*/ | |||||
protected function degradeAccount() | |||||
{ | |||||
// The account may be already deleted, or degraded | |||||
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, | |||||
) | |||||
); | |||||
$this->sendMail(\App\Mail\NegativeBalanceDegraded::class, true); | |||||
} | |||||
/** | |||||
* 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) { | |||||
$email = $this->wallet->owner->email; | |||||
$this->wallet->owner->delete(); | |||||
\Log::info( | |||||
sprintf( | |||||
"[WalletCheck] Account deleted %s (%s)", | |||||
$this->wallet->id, | |||||
) | |||||
); | |||||
} | |||||
} | |||||
/** | |||||
* Send the email | * Send the email | ||||
* | * | ||||
* @param string $class Mailable class name | * @param string $class Mailable class name | ||||
* @param bool $with_external Use users's external email | * @param bool $with_external Use users's external email | ||||
*/ | */ | ||||
protected function sendMail($class, $with_external = false): void | protected function sendMail($class, $with_external = false): void | ||||
{ | { | ||||
// TODO: Send the email to all wallet controllers? | // TODO: Send the email to all wallet controllers? | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | public static function threshold(Wallet $wallet, string $type): ?Carbon | ||||
return $negative_since->addHours(1); | return $negative_since->addHours(1); | ||||
} | } | ||||
$thresholds = [ | $thresholds = [ | ||||
// A day before the second reminder | // A day before the second reminder | ||||
self::THRESHOLD_BEFORE_REMINDER => 7 - 1, | self::THRESHOLD_BEFORE_REMINDER => 7 - 1, | ||||
// Second notification | // Second notification | ||||
self::THRESHOLD_REMINDER => 7, | 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, | |||||
// Last chance to top-up the wallet | // Last chance to top-up the wallet | ||||
self::THRESHOLD_BEFORE_DEGRADE => 13, | self::THRESHOLD_BEFORE_DEGRADE => 13, | ||||
// Account degradation | // Account degradation | ||||
self::THRESHOLD_DEGRADE => 14, | self::THRESHOLD_DEGRADE => 14, | ||||
]; | ]; | ||||
if (!empty($thresholds[$type])) { | if (!empty($thresholds[$type])) { | ||||
return $negative_since->addDays($thresholds[$type]); | return $negative_since->addDays($thresholds[$type]); | ||||
Show All 13 Lines |