diff --git a/src/app/Console/Commands/OwnerSwapCommand.php b/src/app/Console/Commands/OwnerSwapCommand.php index 9534cbea..bd59c3fb 100644 --- a/src/app/Console/Commands/OwnerSwapCommand.php +++ b/src/app/Console/Commands/OwnerSwapCommand.php @@ -1,113 +1,115 @@ argument('current-user') == $this->argument('target-user')) { $this->error('Users cannot be the same.'); return 1; } $user = $this->getUser($this->argument('current-user')); if (!$user) { $this->error('User not found.'); return 1; } $target = $this->getUser($this->argument('target-user')); if (!$target) { $this->error('User not found.'); return 1; } $wallet = $user->wallets->first(); $target_wallet = $target->wallets->first(); if ($wallet->id != $target->wallet()->id) { $this->error('The target user does not belong to the same account.'); return 1; } Queue::fake(); DB::beginTransaction(); // Switch wallet for existing entitlements $wallet->entitlements()->withTrashed()->update(['wallet_id' => $target_wallet->id]); - // Update target user created_at + // Update target user's created_at timestamp to the source user's created_at. + // This is needed because we use this date when charging entitlements, + // i.e. the first month is free. $dt = \now()->subMonthsWithoutOverflow(1); - if ($target->created_at >= $dt) { - $target->created_at = $dt; + if ($target->created_at > $dt && $target->created_at > $user->created_at) { + $target->created_at = $user->created_at; $target->save(); } // Migrate wallet properties $target_wallet->balance = $wallet->balance; $target_wallet->currency = $wallet->currency; $target_wallet->save(); $wallet->balance = 0; $wallet->save(); // Migrate wallet settings $settings = $wallet->settings()->get(); \App\WalletSetting::where('wallet_id', $wallet->id)->delete(); \App\WalletSetting::where('wallet_id', $target_wallet->id)->delete(); foreach ($settings as $setting) { $target_wallet->setSetting($setting->key, $setting->value); } DB::commit(); // Update mollie/stripe customer email (which point to the old wallet id) $this->updatePaymentCustomer($target_wallet); } /** * Update user/wallet metadata at payment provider * * @param \App\Wallet $wallet The wallet */ private function updatePaymentCustomer(\App\Wallet $wallet): void { if ($mollie_id = $wallet->getSetting('mollie_id')) { mollie()->customers()->update($mollie_id, [ 'name' => $wallet->owner->name(), 'email' => $wallet->id . '@private.' . \config('app.domain'), ]); } // TODO: Stripe } } diff --git a/src/tests/Feature/Console/OwnerSwapTest.php b/src/tests/Feature/Console/OwnerSwapTest.php index 51a9103c..7b0b6bec 100644 --- a/src/tests/Feature/Console/OwnerSwapTest.php +++ b/src/tests/Feature/Console/OwnerSwapTest.php @@ -1,140 +1,142 @@ deleteTestUser('user1@owner-swap.com'); $this->deleteTestUser('user2@owner-swap.com'); $this->deleteTestDomain('owner-swap.com'); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestUser('user1@owner-swap.com'); $this->deleteTestUser('user2@owner-swap.com'); $this->deleteTestDomain('owner-swap.com'); parent::tearDown(); } /** * Test the command * * @group mollie */ public function testHandle(): void { Queue::fake(); // Create some sample account $owner = $this->getTestUser('user1@owner-swap.com'); $user = $this->getTestUser('user2@owner-swap.com'); $domain = $this->getTestDomain('owner-swap.com', [ 'status' => \App\Domain::STATUS_NEW, 'type' => \App\Domain::TYPE_HOSTED, ]); $package_kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first(); $package_domain = \App\Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $owner->assignPackage($package_kolab); $owner->assignPackage($package_kolab, $user); $domain->assignPackage($package_domain, $owner); $wallet = $owner->wallets()->first(); $wallet->currency = 'USD'; $wallet->balance = 100; $wallet->save(); $wallet->setSetting('test', 'test'); $target_wallet = $user->wallets()->first(); + $owner->created_at = \now()->subMonths(1); + $owner->save(); $entitlements = $wallet->entitlements()->orderBy('id')->pluck('id')->all(); $this->assertCount(15, $entitlements); $this->assertSame(0, $target_wallet->entitlements()->count()); $customer = $this->createMollieCustomer($wallet); // Non-existing target user $code = \Artisan::call("owner:swap user1@owner-swap.com unknown@unknown.org"); $output = trim(\Artisan::output()); $this->assertSame(1, $code); $this->assertSame("User not found.", $output); // The same user $code = \Artisan::call("owner:swap user1@owner-swap.com user1@owner-swap.com"); $output = trim(\Artisan::output()); $this->assertSame(1, $code); $this->assertSame("Users cannot be the same.", $output); // Success $code = \Artisan::call("owner:swap user1@owner-swap.com user2@owner-swap.com"); $output = trim(\Artisan::output()); $this->assertSame(0, $code); $this->assertSame("", $output); $user->refresh(); $target_wallet->refresh(); $target_entitlements = $target_wallet->entitlements()->orderBy('id')->pluck('id')->all(); $this->assertSame($target_entitlements, $entitlements); $this->assertSame(0, $wallet->entitlements()->count()); $this->assertSame($wallet->balance, $target_wallet->balance); $this->assertSame($wallet->currency, $target_wallet->currency); - $this->assertTrue($user->created_at <= \now()->subMonthsWithoutOverflow(1)); + $this->assertTrue($user->created_at->toDateTimeString() === $owner->created_at->toDateTimeString()); $this->assertSame('test', $target_wallet->getSetting('test')); $wallet->refresh(); $this->assertSame(null, $wallet->getSetting('test')); $this->assertSame(0, $wallet->balance); $target_customer = $this->getMollieCustomer($target_wallet->getSetting('mollie_id')); $this->assertSame($customer->id, $target_customer->id); $this->assertTrue($customer->email != $target_customer->email); $this->assertSame($target_wallet->id . '@private.' . \config('app.domain'), $target_customer->email); // Test case when the target user does not belong to the same account $john = $this->getTestUser('john@kolab.org'); $owner->entitlement()->update(['wallet_id' => $john->wallets->first()->id]); $code = \Artisan::call("owner:swap user2@owner-swap.com user1@owner-swap.com"); $output = trim(\Artisan::output()); $this->assertSame(1, $code); $this->assertSame("The target user does not belong to the same account.", $output); } /** * Create a Mollie customer */ private function createMollieCustomer($wallet) { $customer = mollie()->customers()->create([ 'name' => $wallet->owner->name(), 'email' => $wallet->id . '@private.' . \config('app.domain'), ]); $customer_id = $customer->id; $wallet->setSetting('mollie_id', $customer->id); return $customer; } /** * Get a Mollie customer */ private function getMollieCustomer(string $mollie_id) { return mollie()->customers()->get($mollie_id); } }