Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117906217
D3833.1775389733.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
31 KB
Referenced Files
None
Subscribers
None
D3833.1775389733.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D3833: Per-plan trial period
Attached
Detach File
Event Timeline