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 @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Http\Controllers\API\V4\PaymentsController; use App\Wallet; use Carbon\Carbon; use Illuminate\Bus\Queueable; @@ -21,7 +22,9 @@ 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_BEFORE_REMINDER = 'before_reminder'; public const THRESHOLD_INITIAL = 'initial'; /** @var int The number of seconds to wait before retrying the job. */ @@ -52,12 +55,12 @@ /** * Execute the job. * - * @return void + * @return ?string Executed action (THRESHOLD_*) */ public function handle() { if ($this->wallet->balance >= 0) { - return; + return null; } $now = Carbon::now(); @@ -65,32 +68,46 @@ // Delete the account if (self::threshold($this->wallet, self::THRESHOLD_DELETE) < $now) { $this->deleteAccount(); - return; + return self::THRESHOLD_DELETE; } // Warn about the upcomming account deletion if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_DELETE) < $now) { $this->warnBeforeDelete(); - return; + return self::THRESHOLD_BEFORE_DELETE; } // Suspend the account if (self::threshold($this->wallet, self::THRESHOLD_SUSPEND) < $now) { $this->suspendAccount(); - return; + return self::THRESHOLD_SUSPEND; + } + + // 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 second reminder if (self::threshold($this->wallet, self::THRESHOLD_REMINDER) < $now) { $this->secondReminder(); - return; + return self::THRESHOLD_REMINDER; + } + + // 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; } // Send the initial reminder if (self::threshold($this->wallet, self::THRESHOLD_INITIAL) < $now) { $this->initialReminder(); - return; + return self::THRESHOLD_INITIAL; } + + return null; } /** @@ -295,11 +312,21 @@ 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) { diff --git a/src/tests/Feature/Jobs/WalletCheckTest.php b/src/tests/Feature/Jobs/WalletCheckTest.php --- a/src/tests/Feature/Jobs/WalletCheckTest.php +++ b/src/tests/Feature/Jobs/WalletCheckTest.php @@ -115,10 +115,36 @@ } /** - * Test job handle, reminder notification + * Test job handle, top-up before reminder notification * * @depends testHandleInitial */ + public function testHandleBeforeReminder(): void + { + Mail::fake(); + + $user = $this->getTestUser('ned@kolab.org'); + $wallet = $user->wallets()->first(); + $now = Carbon::now(); + + // Balance turned negative 7-1 days ago + $wallet->setSetting('balance_negative_since', $now->subDays(7 - 1)->toDateTimeString()); + + $job = new WalletCheck($wallet); + $res = $job->handle(); + + Mail::assertNothingSent(); + + // TODO: Test that it actually executed the topUpWallet() + $this->assertSame(WalletCheck::THRESHOLD_BEFORE_REMINDER, $res); + $this->assertFalse($user->fresh()->isSuspended()); + } + + /** + * Test job handle, reminder notification + * + * @depends testHandleBeforeReminder + */ public function testHandleReminder(): void { Mail::fake(); @@ -149,10 +175,37 @@ } /** - * Test job handle, account suspending + * Test job handle, top-up wallet before account suspending * * @depends testHandleReminder */ + public function testHandleBeforeSuspended(): void + { + Mail::fake(); + + $user = $this->getTestUser('ned@kolab.org'); + $wallet = $user->wallets()->first(); + $now = Carbon::now(); + + // Balance turned negative 7+14-1 days ago + $days = 7 + 14 - 1; + $wallet->setSetting('balance_negative_since', $now->subDays($days)->toDateTimeString()); + + $job = new WalletCheck($wallet); + $res = $job->handle(); + + Mail::assertNothingSent(); + + // TODO: Test that it actually executed the topUpWallet() + $this->assertSame(WalletCheck::THRESHOLD_BEFORE_SUSPEND, $res); + $this->assertFalse($user->fresh()->isSuspended()); + } + + /** + * Test job handle, account suspending + * + * @depends testHandleBeforeSuspended + */ public function testHandleSuspended(): void { Mail::fake(); diff --git a/src/tests/Functional/Methods/DomainTest.php b/src/tests/Functional/Methods/DomainTest.php --- a/src/tests/Functional/Methods/DomainTest.php +++ b/src/tests/Functional/Methods/DomainTest.php @@ -110,5 +110,4 @@ $this->assertFalse($this->domain->isConfirmed()); $this->assertTrue($this->domain->isVerified()); } - }