Page MenuHomePhorge

D3833.1775389733.diff
No OneTemporary

Authored By
Unknown
Size
31 KB
Referenced Files
None
Subscribers
None

D3833.1775389733.diff

diff --git a/src/app/Console/Commands/Wallet/TrialEndCommand.php b/src/app/Console/Commands/Wallet/TrialEndCommand.php
--- a/src/app/Console/Commands/Wallet/TrialEndCommand.php
+++ b/src/app/Console/Commands/Wallet/TrialEndCommand.php
@@ -54,6 +54,12 @@
->cursor();
foreach ($wallets as $wallet) {
+ // Skip accounts with no trial period, or a period longer than a month
+ $plan = $wallet->plan();
+ if (!$plan || $plan->free_months != 1) {
+ continue;
+ }
+
// Send the email asynchronously
\App\Jobs\TrialEndEmail::dispatch($wallet->owner);
diff --git a/src/app/Entitlement.php b/src/app/Entitlement.php
--- a/src/app/Entitlement.php
+++ b/src/app/Entitlement.php
@@ -47,26 +47,6 @@
];
/**
- * Return the costs per day for this entitlement.
- *
- * @return float
- */
- public function costsPerDay()
- {
- if ($this->cost == 0) {
- return (float) 0;
- }
-
- $discount = $this->wallet->getDiscountRate();
-
- $daysInLastMonth = \App\Utils::daysInLastMonth();
-
- $costsPerDay = (float) ($this->cost * $discount) / $daysInLastMonth;
-
- return $costsPerDay;
- }
-
- /**
* Create a transaction record for this entitlement.
*
* @param string $type The type of transaction ('created', 'billed', 'deleted'), but use the
diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/WalletsController.php
@@ -232,10 +232,14 @@
return null;
}
- // the owner was created less than a month ago
- if ($wallet->owner->created_at > Carbon::now()->subMonthsWithoutOverflow(1)) {
- // but more than two weeks ago, notice of trial ending
- if ($wallet->owner->created_at <= Carbon::now()->subWeeks(2)) {
+ $plan = $wallet->plan();
+ $freeMonths = $plan ? $plan->free_months : 0;
+ $trialEnd = $freeMonths ? $wallet->owner->created_at->copy()->addMonthsWithoutOverflow($freeMonths) : null;
+
+ // the owner is still in the trial period
+ if ($trialEnd && $trialEnd > Carbon::now()) {
+ // notice of trial ending if less than 2 weeks left
+ if ($trialEnd < Carbon::now()->addWeeks(2)) {
return \trans('app.wallet-notice-trial-end');
}
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
@@ -114,15 +114,22 @@
return;
}
- // Determine if we're still within the free first month
- $freeMonthEnds = $owner->created_at->copy()->addMonthsWithoutOverflow(1);
+ $now = Carbon::now();
- if ($freeMonthEnds >= Carbon::now()) {
- return;
+ // Determine if we're still within the trial period
+ $trial = $entitlement->wallet->trialInfo();
+ if (
+ !empty($trial)
+ && $entitlement->updated_at < $trial['end']
+ && in_array($entitlement->sku_id, $trial['skus'])
+ ) {
+ if ($trial['end'] >= $now) {
+ return;
+ }
+
+ $entitlement->updated_at = $trial['end'];
}
- $now = Carbon::now();
-
// get the discount rate applied to the wallet.
$discount = $entitlement->wallet->getDiscountRate();
@@ -168,6 +175,8 @@
return;
}
+ // FIXME: Shouldn't we create per-entitlement transaction record?
+
$entitlement->wallet->debit($cost);
}
}
diff --git a/src/app/Plan.php b/src/app/Plan.php
--- a/src/app/Plan.php
+++ b/src/app/Plan.php
@@ -18,6 +18,7 @@
* @property string $description
* @property int $discount_qty
* @property int $discount_rate
+ * @property int $free_months
* @property string $id
* @property string $mode Plan signup mode (email|token)
* @property string $name
@@ -48,6 +49,8 @@
'discount_qty',
// the rate of the discount for this plan
'discount_rate',
+ // number of free months (trial)
+ 'free_months',
];
/** @var array<string, string> The attributes that should be cast */
@@ -55,7 +58,8 @@
'promo_from' => 'datetime:Y-m-d H:i:s',
'promo_to' => 'datetime:Y-m-d H:i:s',
'discount_qty' => 'integer',
- 'discount_rate' => 'integer'
+ 'discount_rate' => 'integer',
+ 'free_months' => 'integer'
];
/** @var array<int, string> Translatable properties */
diff --git a/src/app/Wallet.php b/src/app/Wallet.php
--- a/src/app/Wallet.php
+++ b/src/app/Wallet.php
@@ -75,83 +75,77 @@
*/
public function chargeEntitlements($apply = true): int
{
- // This wallet has been created less than a month ago, this is the trial period
- if ($this->owner->created_at >= Carbon::now()->subMonthsWithoutOverflow(1)) {
- // Move all the current entitlement's updated_at timestamps forward to one month after
- // this wallet was created.
- $freeMonthEnds = $this->owner->created_at->copy()->addMonthsWithoutOverflow(1);
-
- foreach ($this->entitlements()->get()->fresh() as $entitlement) {
- if ($entitlement->updated_at < $freeMonthEnds) {
- $entitlement->updated_at = $freeMonthEnds;
- $entitlement->save();
- }
- }
-
- return 0;
- }
-
+ $transactions = [];
$profit = 0;
$charges = 0;
$discount = $this->getDiscountRate();
$isDegraded = $this->owner->isDegraded();
+ $trial = $this->trialInfo();
if ($apply) {
DB::beginTransaction();
}
- // used to parent individual entitlement billings to the wallet debit.
- $entitlementTransactions = [];
-
- foreach ($this->entitlements()->get() as $entitlement) {
- // This entitlement has been created less than or equal to 14 days ago (this is at
+ // Get all entitlements...
+ $entitlements = $this->entitlements()
+ // Skip entitlements created less than or equal to 14 days ago (this is at
// maximum the fourteenth 24-hour period).
- if ($entitlement->created_at > Carbon::now()->subDays(14)) {
- continue;
- }
+ // ->where('created_at', '<=', Carbon::now()->subDays(14))
+ // Skip entitlements created, or billed last, less than a month ago.
+ ->where('updated_at', '<=', Carbon::now()->subMonthsWithoutOverflow(1))
+ ->get();
+
+ foreach ($entitlements as $entitlement) {
+ // If in trial, move entitlement's updated_at timestamps forward to the trial end.
+ if (
+ !empty($trial)
+ && $entitlement->updated_at < $trial['end']
+ && in_array($entitlement->sku_id, $trial['skus'])
+ ) {
+ // TODO: Consider not updating the updated_at to a future date, i.e. bump it
+ // as many months as possible, but not into the future
+ // if we're in dry-run, you know...
+ if ($apply) {
+ $entitlement->updated_at = $trial['end'];
+ $entitlement->save();
+ }
- // This entitlement was created, or billed last, less than a month ago.
- if ($entitlement->updated_at > Carbon::now()->subMonthsWithoutOverflow(1)) {
continue;
}
- // updated last more than a month ago -- was it billed?
- if ($entitlement->updated_at <= Carbon::now()->subMonthsWithoutOverflow(1)) {
- $diff = $entitlement->updated_at->diffInMonths(Carbon::now());
+ $diff = $entitlement->updated_at->diffInMonths(Carbon::now());
- $cost = (int) ($entitlement->cost * $discount * $diff);
- $fee = (int) ($entitlement->fee * $diff);
-
- if ($isDegraded) {
- $cost = 0;
- }
+ if ($diff <= 0) {
+ continue;
+ }
- $charges += $cost;
- $profit += $cost - $fee;
+ $cost = (int) ($entitlement->cost * $discount * $diff);
+ $fee = (int) ($entitlement->fee * $diff);
- // if we're in dry-run, you know...
- if (!$apply) {
- continue;
- }
+ if ($isDegraded) {
+ $cost = 0;
+ }
- $entitlement->updated_at = $entitlement->updated_at->copy()
- ->addMonthsWithoutOverflow($diff);
+ $charges += $cost;
+ $profit += $cost - $fee;
- $entitlement->save();
+ // if we're in dry-run, you know...
+ if (!$apply) {
+ continue;
+ }
- if ($cost == 0) {
- continue;
- }
+ $entitlement->updated_at = $entitlement->updated_at->copy()->addMonthsWithoutOverflow($diff);
+ $entitlement->save();
- $entitlementTransactions[] = $entitlement->createTransaction(
- Transaction::ENTITLEMENT_BILLED,
- $cost
- );
+ if ($cost == 0) {
+ continue;
}
+
+ $transactions[] = $entitlement->createTransaction(Transaction::ENTITLEMENT_BILLED, $cost);
}
if ($apply) {
- $this->debit($charges, '', $entitlementTransactions);
+ $this->debit($charges, '', $transactions);
// Credit/debit the reseller
if ($profit != 0 && $this->owner->tenant) {
@@ -182,26 +176,53 @@
return null;
}
- // retrieve any expected charges
- $expectedCharge = $this->expectedCharges();
-
- // get the costs per day for all entitlements billed against this wallet
- $costsPerDay = $this->costsPerDay();
+ $balance = $this->balance;
+ $discount = $this->getDiscountRate();
+ $trial = $this->trialInfo();
+
+ // Get all entitlements...
+ $entitlements = $this->entitlements()->orderBy('updated_at')->get()
+ ->filter(function ($entitlement) {
+ return $entitlement->cost > 0;
+ })
+ ->map(function ($entitlement) {
+ return [
+ 'date' => $entitlement->updated_at ?: $entitlement->created_at,
+ 'cost' => $entitlement->cost,
+ 'sku_id' => $entitlement->sku_id,
+ ];
+ })
+ ->all();
+
+ $max = 12 * 25;
+ while ($max > 0) {
+ foreach ($entitlements as &$entitlement) {
+ $until = $entitlement['date'] = $entitlement['date']->addMonthsWithoutOverflow(1);
+
+ if (
+ !empty($trial)
+ && $entitlement['date'] < $trial['end']
+ && in_array($entitlement['sku_id'], $trial['skus'])
+ ) {
+ continue;
+ }
- if (!$costsPerDay) {
- return null;
- }
+ $balance -= (int) ($entitlement['cost'] * $discount);
- // the number of days this balance, minus the expected charges, would last
- $daysDelta = floor(($this->balance - $expectedCharge) / $costsPerDay);
+ if ($balance < 0) {
+ break 2;
+ }
+ }
- // calculate from the last entitlement billed
- $entitlement = $this->entitlements()->orderBy('updated_at', 'desc')->first();
+ $max--;
+ }
- $until = $entitlement->updated_at->copy()->addDays($daysDelta);
+ if (empty($until)) {
+ return null;
+ }
// Don't return dates from the past
- if ($until < Carbon::now() && !$until->isToday()) {
+ if ($until <= Carbon::now() && !$until->isToday()) {
return null;
}
@@ -224,22 +245,6 @@
}
/**
- * Retrieve the costs per day of everything charged to this wallet.
- *
- * @return float
- */
- public function costsPerDay()
- {
- $costs = (float) 0;
-
- foreach ($this->entitlements as $entitlement) {
- $costs += $entitlement->costsPerDay();
- }
-
- return $costs;
- }
-
- /**
* Add an amount of pecunia to this wallet's balance.
*
* @param int $amount The amount of pecunia to add (in cents).
@@ -406,6 +411,18 @@
}
/**
+ * Plan of the wallet.
+ *
+ * @return ?\App\Plan
+ */
+ public function plan()
+ {
+ $planId = $this->owner->getSetting('plan_id');
+
+ return $planId ? Plan::find($planId) : null;
+ }
+
+ /**
* Remove a controller from this wallet.
*
* @param \App\User $user The user to remove as a controller from this wallet.
@@ -426,12 +443,50 @@
*/
public function transactions()
{
- return Transaction::where(
- [
- 'object_id' => $this->id,
- 'object_type' => Wallet::class
- ]
- );
+ return Transaction::where('object_id', $this->id)->where('object_type', Wallet::class);
+ }
+
+ /**
+ * Returns trial related information.
+ *
+ * @return ?array Plan ID, plan SKUs, trial end date, number of free months (planId, skus, end, months)
+ */
+ public function trialInfo(): ?array
+ {
+ $plan = $this->plan();
+ $freeMonths = $plan ? $plan->free_months : 0;
+ $trialEnd = $freeMonths ? $this->owner->created_at->copy()->addMonthsWithoutOverflow($freeMonths) : null;
+
+ if ($trialEnd) {
+ // Get all SKUs assigned to the plan (they are free in trial)
+ // TODO: We could store the list of plan's SKUs in the wallet settings, for two reasons:
+ // - performance
+ // - if we change plan definition at some point in time, the old users would use
+ // the old definition, instead of the current one
+ // TODO: The same for plan's free_months value
+ $trialSkus = \App\Sku::select('id')
+ ->whereIn('id', function ($query) use ($plan) {
+ $query->select('sku_id')
+ ->from('package_skus')
+ ->whereIn('package_id', function ($query) use ($plan) {
+ $query->select('package_id')
+ ->from('plan_packages')
+ ->where('plan_id', $plan->id);
+ });
+ })
+ ->whereNot('title', 'storage')
+ ->pluck('id')
+ ->all();
+
+ return [
+ 'end' => $trialEnd,
+ 'skus' => $trialSkus,
+ 'planId' => $plan->id,
+ 'months' => $freeMonths,
+ ];
+ }
+
+ return null;
}
/**
diff --git a/src/database/migrations/2022_09_08_100000_plans_free_months.php b/src/database/migrations/2022_09_08_100000_plans_free_months.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2022_09_08_100000_plans_free_months.php
@@ -0,0 +1,41 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::table(
+ 'plans',
+ function (Blueprint $table) {
+ $table->tinyInteger('free_months')->unsigned()->default(0);
+ }
+ );
+
+ DB::table('plans')->update(['free_months' => 1]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table(
+ 'plans',
+ function (Blueprint $table) {
+ $table->dropColumn('free_months');
+ }
+ );
+ }
+};
diff --git a/src/database/seeds/local/PlanSeeder.php b/src/database/seeds/local/PlanSeeder.php
--- a/src/database/seeds/local/PlanSeeder.php
+++ b/src/database/seeds/local/PlanSeeder.php
@@ -32,6 +32,7 @@
'title' => 'individual',
'name' => 'Individual Account',
'description' => $description,
+ 'free_months' => 1,
'discount_qty' => 0,
'discount_rate' => 0
]
@@ -59,6 +60,7 @@
'title' => 'group',
'name' => 'Group Account',
'description' => $description,
+ 'free_months' => 1,
'discount_qty' => 0,
'discount_rate' => 0
]
@@ -91,6 +93,7 @@
[
'title' => 'individual',
'name' => 'Individual Account',
+ 'free_months' => 1,
'description' => $description,
'discount_qty' => 0,
'discount_rate' => 0
@@ -122,6 +125,7 @@
'title' => 'group',
'name' => 'Group Account',
'description' => $description,
+ 'free_months' => 1,
'discount_qty' => 0,
'discount_rate' => 0
]
diff --git a/src/database/seeds/production/PlanSeeder.php b/src/database/seeds/production/PlanSeeder.php
--- a/src/database/seeds/production/PlanSeeder.php
+++ b/src/database/seeds/production/PlanSeeder.php
@@ -32,6 +32,7 @@
'title' => 'individual',
'name' => 'Individual Account',
'description' => $description,
+ 'free_months' => 1,
'discount_qty' => 0,
'discount_rate' => 0
]
@@ -59,6 +60,7 @@
'title' => 'group',
'name' => 'Group Account',
'description' => $description,
+ 'free_months' => 1,
'discount_qty' => 0,
'discount_rate' => 0
]
diff --git a/src/tests/Feature/Console/Wallet/TrialEndTest.php b/src/tests/Feature/Console/Wallet/TrialEndTest.php
--- a/src/tests/Feature/Console/Wallet/TrialEndTest.php
+++ b/src/tests/Feature/Console/Wallet/TrialEndTest.php
@@ -38,12 +38,12 @@
{
Queue::fake();
- $package = \App\Package::withEnvTenantContext()->where('title', 'lite')->first();
+ $plan = \App\Plan::withEnvTenantContext()->where('title', 'individual')->first();
$user = $this->getTestUser('test-user1@kolabnow.com', [
'status' => User::STATUS_IMAP_READY | User::STATUS_LDAP_READY | User::STATUS_ACTIVE,
]);
$wallet = $user->wallets()->first();
- $user->assignPackage($package);
+ $user->assignPlan($plan);
DB::table('users')->update(['created_at' => \now()->clone()->subMonthsNoOverflow(2)->subHours(1)]);
@@ -104,6 +104,7 @@
$user2 = $this->getTestUser('test-user2@kolabnow.com', [
'status' => User::STATUS_IMAP_READY | User::STATUS_LDAP_READY | User::STATUS_ACTIVE,
]);
+ $package = \App\Package::withEnvTenantContext()->where('title', 'lite')->first();
$user->assignPackage($package, $user2);
$user2->created_at = \now()->clone()->subMonthsNoOverflow(1);
$user2->save();
diff --git a/src/tests/Feature/Controller/WalletsTest.php b/src/tests/Feature/Controller/WalletsTest.php
--- a/src/tests/Feature/Controller/WalletsTest.php
+++ b/src/tests/Feature/Controller/WalletsTest.php
@@ -37,8 +37,8 @@
public function testGetWalletNotice(): void
{
$user = $this->getTestUser('wallets-controller@kolabnow.com');
- $package = \App\Package::withObjectTenantContext($user)->where('title', 'kolab')->first();
- $user->assignPackage($package);
+ $plan = \App\Plan::withObjectTenantContext($user)->where('title', 'individual')->first();
+ $user->assignPlan($plan);
$wallet = $user->wallets()->first();
$controller = new WalletsController();
@@ -50,7 +50,7 @@
$this->assertSame('You are in your free trial period.', $notice);
- $wallet->owner->created_at = Carbon::now()->subDays(15);
+ $wallet->owner->created_at = Carbon::now()->subWeeks(3);
$wallet->owner->save();
$notice = $method->invoke($controller, $wallet);
@@ -64,8 +64,8 @@
$this->assertSame('You are out of credit, top up your balance now.', $notice);
// User/entitlements created slightly more than a month ago, balance=9,99 CHF (monthly)
- $wallet->owner->created_at = Carbon::now()->subMonthsWithoutOverflow(1)->subDays(1);
- $wallet->owner->save();
+ $this->backdateEntitlements($wallet->entitlements, Carbon::now()->subMonthsWithoutOverflow(1)->subDays(1));
+ $wallet->refresh();
// test "1 month"
$wallet->balance = 990;
@@ -77,7 +77,7 @@
$wallet->balance = 990 * 2.6;
$notice = $method->invoke($controller, $wallet);
- $this->assertMatchesRegularExpression('/\(2 months 2 weeks\)/', $notice);
+ $this->assertMatchesRegularExpression('/\(1 month 4 weeks\)/', $notice);
// Change locale to make sure the text is localized by Carbon
\app()->setLocale('de');
@@ -86,7 +86,7 @@
$wallet->balance = 990 * 23.5;
$notice = $method->invoke($controller, $wallet);
- $this->assertMatchesRegularExpression('/\(1 Jahr 11 Monate\)/', $notice);
+ $this->assertMatchesRegularExpression('/\(1 Jahr 10 Monate\)/', $notice);
// Old entitlements, 100% discount
$this->backdateEntitlements($wallet->entitlements, Carbon::now()->subDays(40));
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
@@ -40,28 +40,6 @@
}
/**
- * Test for Entitlement::costsPerDay()
- */
- public function testCostsPerDay(): void
- {
- // 500
- // 28 days: 17.86
- // 31 days: 16.129
- $user = $this->getTestUser('entitlement-test@kolabnow.com');
- $package = Package::withEnvTenantContext()->where('title', 'kolab')->first();
- $mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first();
-
- $user->assignPackage($package);
-
- $entitlement = $user->entitlements->where('sku_id', $mailbox->id)->first();
-
- $costsPerDay = $entitlement->costsPerDay();
-
- $this->assertTrue($costsPerDay < 17.86);
- $this->assertTrue($costsPerDay > 16.12);
- }
-
- /**
* Tests for entitlements
* @todo This really should be in User or Wallet tests file
*/
diff --git a/src/tests/Feature/WalletTest.php b/src/tests/Feature/WalletTest.php
--- a/src/tests/Feature/WalletTest.php
+++ b/src/tests/Feature/WalletTest.php
@@ -3,6 +3,7 @@
namespace Tests\Feature;
use App\Package;
+use App\Plan;
use App\User;
use App\Sku;
use App\Transaction;
@@ -27,6 +28,9 @@
'jane@kolabnow.com'
];
+ /**
+ * {@inheritDoc}
+ */
public function setUp(): void
{
parent::setUp();
@@ -35,8 +39,13 @@
foreach ($this->users as $user) {
$this->deleteTestUser($user);
}
+
+ Sku::select()->update(['fee' => 0]);
}
+ /**
+ * {@inheritDoc}
+ */
public function tearDown(): void
{
foreach ($this->users as $user) {
@@ -90,8 +99,8 @@
// 31 days: 31.93 per day
$user = $this->getTestUser('jane@kolabnow.com');
- $package = Package::withEnvTenantContext()->where('title', 'kolab')->first();
- $user->assignPackage($package);
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $user->assignPlan($plan);
$wallet = $user->wallets()->first();
// User/entitlements created today, balance=0
@@ -138,29 +147,6 @@
}
/**
- * Test for Wallet::costsPerDay()
- */
- public function testCostsPerDay(): void
- {
- // 990
- // 28 days: 35.36
- // 31 days: 31.93
- $user = $this->getTestUser('jane@kolabnow.com');
-
- $package = Package::withEnvTenantContext()->where('title', 'kolab')->first();
- $mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first();
-
- $user->assignPackage($package);
-
- $wallet = $user->wallets()->first();
-
- $costsPerDay = $wallet->costsPerDay();
-
- $this->assertTrue($costsPerDay < 35.38);
- $this->assertTrue($costsPerDay > 31.93);
- }
-
- /**
* Verify a wallet is created, when a user is created.
*/
public function testCreateUserCreatesWallet(): void
@@ -332,11 +318,11 @@
// Add 40% fee to all SKUs
Sku::select()->update(['fee' => DB::raw("`cost` * 0.4")]);
- $package = Package::withEnvTenantContext()->where('title', 'kolab')->first();
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
$storage = Sku::withEnvTenantContext()->where('title', 'storage')->first();
- $user->assignPackage($package);
+ $user->assignPlan($plan);
$user->assignSku($storage, 5);
- $user->refresh();
+ $user->setSetting('plan_id', null); // disable plan and trial
// Reset reseller's wallet balance and transactions
$reseller_wallet = $user->tenant->wallet();
@@ -357,10 +343,11 @@
$wallet->refresh();
$reseller_wallet->refresh();
- // TODO: Update these comments with what is actually being used to calculate these numbers
- // 388 + 310 + 17 + 17 = 732
+ // User discount is 30%
+ // Expected: groupware: 490 x 70% + mailbox: 500 x 70% + storage: 5 x round(25x70%) = 778
$this->assertSame(-778, $wallet->balance);
- // 388 - 555 x 40% + 310 - 444 x 40% + 34 - 50 x 40% = 312
+ // Reseller fee is 40%
+ // Expected: groupware: 490 x 30% + mailbox: 500 x 30% + storage: 5 x round(25x30%) = 332
$this->assertSame(332, $reseller_wallet->balance);
$transactions = Transaction::where('object_id', $wallet->id)
@@ -381,7 +368,9 @@
$this->assertSame(-778, $trans->amount);
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type);
- // TODO: Test entitlement transaction records
+ // Assert all entitlements' updated_at timestamp
+ $date = $backdate->addMonthsWithoutOverflow(1);
+ $this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get());
// -----------------------------------
// Test charging on entitlement delete
@@ -439,6 +428,107 @@
}
/**
+ * Test for charging and removing entitlements when in trial
+ */
+ public function testChargeAndDeleteEntitlementsTrial(): void
+ {
+ $user = $this->getTestUser('jane@kolabnow.com');
+ $wallet = $user->wallets()->first();
+
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $storage = Sku::withEnvTenantContext()->where('title', 'storage')->first();
+ $user->assignPlan($plan);
+ $user->assignSku($storage, 5);
+
+ // ------------------------------------
+ // Test normal charging of entitlements
+ // ------------------------------------
+
+ // Backdate and charge entitlements, we're expecting one month to be charged
+ // Set fake NOW date to make simpler asserting results that depend on number of days in current/last month
+ Carbon::setTestNow(Carbon::create(2021, 5, 21, 12));
+ $backdate = Carbon::now()->subWeeks(7);
+ $this->backdateEntitlements($user->entitlements, $backdate);
+ $charge = $wallet->chargeEntitlements();
+ $wallet->refresh();
+
+ // Expected: storage: 5 x 25 = 125 (the rest is free in trial)
+ $this->assertSame($balance = -125, $wallet->balance);
+
+ // Assert wallet transaction
+ $transactions = $wallet->transactions()->get();
+
+ $this->assertCount(1, $transactions);
+ $trans = $transactions[0];
+ $this->assertSame('', $trans->description);
+ $this->assertSame($balance, $trans->amount);
+ $this->assertSame(Transaction::WALLET_DEBIT, $trans->type);
+
+ // Assert entitlement transactions
+ $etransactions = Transaction::where('transaction_id', $trans->id)->get();
+ $this->assertCount(5, $etransactions);
+ $trans = $etransactions[0];
+ $this->assertSame(null, $trans->description);
+ $this->assertSame(25, $trans->amount);
+ $this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type);
+
+ // Assert all entitlements' updated_at timestamp
+ $date = $backdate->addMonthsWithoutOverflow(1);
+ $this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get());
+
+ // Run again, expect no changes
+ $charge = $wallet->chargeEntitlements();
+ $wallet->refresh();
+
+ $this->assertSame($balance, $wallet->balance);
+ $this->assertCount(1, $wallet->transactions()->get());
+ $this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get());
+
+ // -----------------------------------
+ // Test charging on entitlement delete
+ // -----------------------------------
+
+ $wallet->transactions()->delete();
+
+ $user->removeSku($storage, 2);
+
+ $wallet->refresh();
+
+ // we expect the wallet to have been charged for 19 days of use of
+ // 2 deleted storage entitlements: 2 x round(25 / 31 * 19) = 30
+ $this->assertSame($balance -= 30, $wallet->balance);
+
+ // Assert wallet transactions
+ $transactions = $wallet->transactions()->get();
+
+ $this->assertCount(2, $transactions);
+ $trans = $transactions[0];
+ $this->assertSame('', $trans->description);
+ $this->assertSame(-15, $trans->amount);
+ $this->assertSame(Transaction::WALLET_DEBIT, $trans->type);
+ $trans = $transactions[1];
+ $this->assertSame('', $trans->description);
+ $this->assertSame(-15, $trans->amount);
+ $this->assertSame(Transaction::WALLET_DEBIT, $trans->type);
+
+ // Assert entitlement transactions
+ /* Note: Commented out because the observer does not create per-entitlement transactions
+ $etransactions = Transaction::where('transaction_id', $transactions[0]->id)->get();
+ $this->assertCount(1, $etransactions);
+ $trans = $etransactions[0];
+ $this->assertSame(null, $trans->description);
+ $this->assertSame(15, $trans->amount);
+ $this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type);
+ $etransactions = Transaction::where('transaction_id', $transactions[1]->id)->get();
+ $this->assertCount(1, $etransactions);
+ $trans = $etransactions[0];
+ $this->assertSame(null, $trans->description);
+ $this->assertSame(15, $trans->amount);
+ $this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type);
+ */
+ }
+
+ /**
* Tests for updateEntitlements()
*/
public function testUpdateEntitlements(): void

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 5, 11:48 AM (20 h, 25 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833358
Default Alt Text
D3833.1775389733.diff (31 KB)

Event Timeline