diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php --- a/src/app/Observers/EntitlementObserver.php +++ b/src/app/Observers/EntitlementObserver.php @@ -88,4 +88,55 @@ $entitlement->entitleable->updated_at = Carbon::now(); $entitlement->entitleable->save(); } + + public function deleting(Entitlement $entitlement) + { + // Start calculating the costs for the consumption of this entitlement if the + // existing consumption spans >= 14 days. + // anything's free for 14 days + if ($entitlement->created_at >= Carbon::now()->subDays(14)) { + return; + } + + $cost = 0; + + // get the discount rate applied to the wallet. + $discount = $entitlement->wallet->getDiscountRate(); + + // just in case this had not been billed yet, ever + $diffInMonths = $entitlement->updated_at->diffInMonths(Carbon::now()); + $cost += (int) ($entitlement->cost * $discount * $diffInMonths); + + // this moves the hypothetical updated at forward to however many months past the original + $updatedAt = $entitlement->updated_at->copy()->addMonthsWithoutOverflow($diffInMonths); + + // now we have the diff in days since the last "billed" period end. + // This may be an entitlement paid up until February 28th, 2020, with today being March + // 12th 2020. Calculating the costs for the entitlement is based on the daily price for the + // past month -- i.e. $price/29 in the case at hand -- times the number of (full) days in + // between the period end and now. + // + // a) The number of days left in the past month, 1 + // b) The cost divided by the number of days in the past month, for example, 555/29, + // c) a) + Todays day-of-month, 12, so 13. + // + + $diffInDays = $updatedAt->diffInDays(Carbon::now()); + + $dayOfThisMonth = Carbon::now()->day; + + // days in the month for the month prior to this one. + // the price per day is based on the number of days left in the last month + $daysInLastMonth = \App\Utils::daysInLastMonth(); + + $pricePerDay = (float)$entitlement->cost / $daysInLastMonth; + + $cost += (int) (round($pricePerDay * $diffInDays, 0)); + + if ($cost == 0) { + return; + } + + $entitlement->wallet->debit($cost); + } } diff --git a/src/tests/Feature/EntitlementTest.php b/src/tests/Feature/EntitlementTest.php --- a/src/tests/Feature/EntitlementTest.php +++ b/src/tests/Feature/EntitlementTest.php @@ -37,11 +37,11 @@ */ public function testUserAddEntitlement(): void { - $package_domain = Package::where('title', 'domain-hosting')->first(); - $package_kolab = Package::where('title', 'kolab')->first(); + $packageDomain = Package::where('title', 'domain-hosting')->first(); + $packageKolab = Package::where('title', 'kolab')->first(); - $sku_domain = Sku::where('title', 'domain-hosting')->first(); - $sku_mailbox = Sku::where('title', 'mailbox')->first(); + $skuDomain = Sku::where('title', 'domain-hosting')->first(); + $skuMailbox = Sku::where('title', 'mailbox')->first(); $owner = $this->getTestUser('entitlement-test@kolabnow.com'); $user = $this->getTestUser('entitled-user@custom-domain.com'); @@ -54,19 +54,22 @@ ] ); - $domain->assignPackage($package_domain, $owner); + $domain->assignPackage($packageDomain, $owner); - $owner->assignPackage($package_kolab); - $owner->assignPackage($package_kolab, $user); + $owner->assignPackage($packageKolab); + $owner->assignPackage($packageKolab, $user); $wallet = $owner->wallets->first(); $this->assertCount(4, $owner->entitlements()->get()); - $this->assertCount(1, $sku_domain->entitlements()->where('wallet_id', $wallet->id)->get()); - $this->assertCount(2, $sku_mailbox->entitlements()->where('wallet_id', $wallet->id)->get()); + $this->assertCount(1, $skuDomain->entitlements()->where('wallet_id', $wallet->id)->get()); + $this->assertCount(2, $skuMailbox->entitlements()->where('wallet_id', $wallet->id)->get()); $this->assertCount(9, $wallet->entitlements); - $this->backdateEntitlements($owner->entitlements, Carbon::now()->subMonthsWithoutOverflow(1)); + $this->backdateEntitlements( + $owner->entitlements, + Carbon::now()->subMonthsWithoutOverflow(1) + ); $wallet->chargeEntitlements(); @@ -92,17 +95,51 @@ $sku = \App\Sku::where('title', 'mailbox')->first(); $this->assertNotNull($sku); - $entitlement = Entitlement::where('wallet_id', $wallet->id)->where('sku_id', $sku->id)->first(); + $entitlement = Entitlement::where('wallet_id', $wallet->id) + ->where('sku_id', $sku->id)->first(); + $this->assertNotNull($entitlement); - $e_sku = $entitlement->sku; - $this->assertSame($sku->id, $e_sku->id); + $eSKU = $entitlement->sku; + $this->assertSame($sku->id, $eSKU->id); + + $eWallet = $entitlement->wallet; + $this->assertSame($wallet->id, $eWallet->id); + + $eEntitleable = $entitlement->entitleable; + $this->assertEquals($user->id, $eEntitleable->id); + $this->assertTrue($eEntitleable instanceof \App\User); + } + + public function testBillDeletedEntitlement(): void + { + $user = $this->getTestUser('entitlement-test@kolabnow.com'); + $package = \App\Package::where('title', 'kolab')->first(); + + $storage = \App\Sku::where('title', 'storage')->first(); + + $user->assignPackage($package); + // some additional SKUs so we have something to delete. + $user->assignSku($storage, 4); + + $wallet = $user->wallets()->first(); + + $this->backdateEntitlements($user->entitlements, Carbon::now()->subWeeks(7)); + + $charge = $wallet->chargeEntitlements(); + + $this->assertTrue($wallet->balance < 0); + + $balance = $wallet->balance; - $e_wallet = $entitlement->wallet; - $this->assertSame($wallet->id, $e_wallet->id); + // the delta between updated_at and now should be ~3 weeks. this opens it up for deletion. + foreach ($user->entitlements as $entitlement) { + if ($entitlement->sku_id == $storage->id && $entitlement->cost > 0) { + $entitlement->delete(); + } + } - $e_entitleable = $entitlement->entitleable; - $this->assertEquals($user->id, $e_entitleable->id); - $this->assertTrue($e_entitleable instanceof \App\User); + // we expect the wallet to have been charged. + $this->assertTrue($wallet->fresh()->balance < $balance); } }