diff --git a/src/app/Auth/Utils.php b/src/app/Auth/Utils.php
index 6f061da6..ec89c46d 100644
--- a/src/app/Auth/Utils.php
+++ b/src/app/Auth/Utils.php
@@ -1,100 +1,101 @@
 <?php
 
 namespace App\Auth;
 
 use Carbon\Carbon;
 
 class Utils
 {
     /**
      * Create a simple authentication token
      *
      * @param string $userid User identifier
      *
      * @return string|null Encrypted token, Null on failure
      */
     public static function tokenCreate($userid): ?string
     {
         // Note: Laravel's Crypt::encryptString() creates output that is too long
         // We need output string to be max. 127 characters. For that reason
         // we use a custom implementation, and we use user ID instead of login.
 
         $cipher = strtolower(config('app.cipher'));
         $key = config('app.key');
         $iv = random_bytes(openssl_cipher_iv_length($cipher));
 
         $data = $userid . '!' . now()->addSeconds(10)->format('YmdHis');
 
         $value = openssl_encrypt($data, $cipher, $key, 0, $iv, $tag);
 
         if ($value === false) {
             return null;
         }
 
         return trim(base64_encode($iv), '=')
             . '!'
             . trim(base64_encode($tag), '=')
             . '!'
             . trim(base64_encode($value), '=');
     }
 
     /**
      * Vaidate a simple authentication token
      *
      * @param string $token Token
      *
      * @return string|null User identifier, Null on failure
      */
     public static function tokenValidate($token): ?string
     {
         if (!preg_match('|^[a-zA-Z0-9!+/]{50,}$|', $token)) {
             // this isn't a token, probably a normal password
             return null;
         }
 
         [$iv, $tag, $payload] = explode('!', $token);
 
         $iv = base64_decode($iv);
         $tag = base64_decode($tag);
         $payload = base64_decode($payload);
 
         $cipher = strtolower(config('app.cipher'));
         $key = config('app.key');
 
         $decrypted = openssl_decrypt($payload, $cipher, $key, 0, $iv, $tag);
 
         if ($decrypted === false) {
             return null;
         }
 
         $payload = explode('!', $decrypted);
 
-        if (count($payload) != 2
+        if (
+            count($payload) != 2
             || !preg_match('|^[0-9]+$|', $payload[0])
             || !preg_match('|^[0-9]{14}+$|', $payload[1])
         ) {
             // Invalid payload format
             return null;
         }
 
         // Check expiration date
         try {
             $expiry = Carbon::create(
                 (int) substr($payload[1], 0, 4),
                 (int) substr($payload[1], 4, 2),
                 (int) substr($payload[1], 6, 2),
                 (int) substr($payload[1], 8, 2),
                 (int) substr($payload[1], 10, 2),
                 (int) substr($payload[1], 12, 2)
             );
 
             if (now() > $expiry) {
                 return null;
             }
         } catch (\Exception $e) {
             return null;
         }
 
         return $payload[0];
     }
 }
diff --git a/src/app/Console/Commands/Tenant/CreateCommand.php b/src/app/Console/Commands/Tenant/CreateCommand.php
index 547b601e..0e7f3669 100644
--- a/src/app/Console/Commands/Tenant/CreateCommand.php
+++ b/src/app/Console/Commands/Tenant/CreateCommand.php
@@ -1,192 +1,192 @@
 <?php
 
 namespace App\Console\Commands\Tenant;
 
 use App\Console\Command;
 use App\Http\Controllers\API\V4\UsersController;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Queue;
 
 class CreateCommand extends Command
 {
     /**
      * The name and signature of the console command.
      *
      * @var string
      */
     protected $signature = 'tenant:create {user} {domain} {--title=} {--password=}';
 
     /**
      * The console command description.
      *
      * @var string
      */
-    protected $description = "Create a tenant (with a set of SKUs/plans/packages), and a reseller user and domain for it.";
+    protected $description = "Create a tenant (with clonoed plans), and a reseller user and domain for it.";
 
     /**
      * Execute the console command.
      *
      * @return mixed
      */
     public function handle()
     {
         $email = $this->argument('user');
 
         if ($user = \App\User::where('email', $email)->first()) {
             $this->error("The user already exists.");
             return 1;
         }
 
         if ($domain = \App\Domain::where('namespace', $this->argument('domain'))->first()) {
             $this->error("The domain already exists.");
             return 1;
         }
 
 
         DB::beginTransaction();
 
         // Create a tenant
         $tenant = \App\Tenant::create(['title' => $this->option('title')]);
 
         // Clone plans, packages, skus for the tenant
         $sku_map = \App\Sku::withEnvTenantContext()->where('active', true)->get()
             ->mapWithKeys(function ($sku) use ($tenant) {
                 $sku_new = \App\Sku::create([
                         'title' => $sku->title,
                         'name' => $sku->getTranslations('name'),
                         'description' => $sku->getTranslations('description'),
                         'cost' => $sku->cost,
                         'units_free' => $sku->units_free,
                         'period' => $sku->period,
                         'handler_class' => $sku->handler_class,
                         'active' => true,
                         'fee' => $sku->fee,
                 ]);
 
                 $sku_new->tenant_id = $tenant->id;
                 $sku_new->save();
 
                 return [$sku->id => $sku_new->id];
             })
             ->all();
 
         $plan_map = \App\Plan::withEnvTenantContext()->get()
             ->mapWithKeys(function ($plan) use ($tenant) {
                 $plan_new = \App\Plan::create([
                         'title' => $plan->title,
                         'name' => $plan->getTranslations('name'),
                         'description' => $plan->getTranslations('description'),
                         'promo_from' => $plan->promo_from,
                         'promo_to' => $plan->promo_to,
                         'qty_min' => $plan->qty_min,
                         'qty_max' => $plan->qty_max,
                         'discount_qty' => $plan->discount_qty,
                         'discount_rate' => $plan->discount_rate,
                 ]);
 
                 $plan_new->tenant_id = $tenant->id;
                 $plan_new->save();
 
                 return [$plan->id => $plan_new->id];
             })
             ->all();
 
         $package_map = \App\Package::withEnvTenantContext()->get()
             ->mapWithKeys(function ($package) use ($tenant) {
                 $package_new = \App\Package::create([
                         'title' => $package->title,
                         'name' => $package->getTranslations('name'),
                         'description' => $package->getTranslations('description'),
                         'discount_rate' => $package->discount_rate,
                 ]);
 
                 $package_new->tenant_id = $tenant->id;
                 $package_new->save();
 
                 return [$package->id => $package_new->id];
             })
             ->all();
 
         DB::table('package_skus')->whereIn('package_id', array_keys($package_map))->get()
             ->each(function ($item) use ($package_map, $sku_map) {
                 if (isset($sku_map[$item->sku_id])) {
                     DB::table('package_skus')->insert([
                             'qty' => $item->qty,
                             'cost' => $item->cost,
                             'sku_id' => $sku_map[$item->sku_id],
                             'package_id' => $package_map[$item->package_id],
                     ]);
                 }
             });
 
         DB::table('plan_packages')->whereIn('plan_id', array_keys($plan_map))->get()
             ->each(function ($item) use ($package_map, $plan_map) {
                 if (isset($package_map[$item->package_id])) {
                     DB::table('plan_packages')->insert([
                             'qty' => $item->qty,
                             'qty_min' => $item->qty_min,
                             'qty_max' => $item->qty_max,
                             'discount_qty' => $item->discount_qty,
                             'discount_rate' => $item->discount_rate,
                             'plan_id' => $plan_map[$item->plan_id],
                             'package_id' => $package_map[$item->package_id],
                     ]);
                 }
             });
 
         // Disable jobs, they would fail anyway as the TENANT_ID is different
         // TODO: We could probably do config(['app.tenant' => $tenant->id]) here
         Queue::fake();
 
         // Make sure the transaction wasn't aborted
         $tenant = \App\Tenant::find($tenant->id);
 
         if (!$tenant) {
             $this->error("Failed to create a tenant.");
             return 1;
         }
 
         $this->info("Created tenant {$tenant->id}.");
 
         // Set up the primary tenant domain
         $domain = \App\Domain::create(
             [
                 'namespace' => $this->argument('domain'),
                 'type' => \App\Domain::TYPE_PUBLIC,
             ]
         );
         $domain->tenant_id = $tenant->id;
         $domain->status = \App\Domain::STATUS_CONFIRMED | \App\Domain::STATUS_ACTIVE;
         $domain->save();
         $this->info("Created domain {$domain->id}.");
 
         $user = new \App\User();
         $user->email = $email;
         $user->password = $this->option('password');
         $user->role = 'reseller';
         $user->tenant_id = $tenant->id;
 
         if ($error = UsersController::validateEmail($email, $user)) {
             $this->error("{$email}: {$error}");
             return 1;
         }
 
         $user->save();
         $this->info("Created user {$user->id}.");
 
         $tenant->setSettings([
             "app.name" => $this->option("title"),
             "app.url" => $this->argument("domain"),
             "app.public_url" => "https://" . $this->argument("domain"),
             "app.support_url" => "https://" . $this->argument("domain") . "/support",
             "mail.sender.address" => "noreply@" . $this->argument("domain"),
             "mail.sender.name" => $this->option("title"),
             "mail.replyto.address" => "noreply@" . $this->argument("domain"),
             "mail.replyto.name" => $this->option("title"),
         ]);
 
         DB::commit();
 
         $this->info("Applied default tenant settings.");
     }
 }
diff --git a/src/app/Console/ObjectCreateCommand.php b/src/app/Console/ObjectCreateCommand.php
index 8914b73d..16789880 100644
--- a/src/app/Console/ObjectCreateCommand.php
+++ b/src/app/Console/ObjectCreateCommand.php
@@ -1,85 +1,86 @@
 <?php
 
 namespace App\Console;
 
 /**
  * This abstract class provides a means to treat objects in our model using CRUD.
  */
 abstract class ObjectCreateCommand extends ObjectCommand
 {
     /** @var ?array Object properties */
     protected $properties;
 
     public function __construct()
     {
         $this->description = "Create a {$this->objectName}";
         $this->signature = sprintf(
             "%s%s:create",
             $this->commandPrefix ? $this->commandPrefix . ":" : "",
             $this->objectName
         );
 
         foreach ($this->getClassProperties() as $fillable) {
             $this->signature .= " {--{$fillable}=}";
         }
 
         parent::__construct();
     }
 
     /**
      * Return list of fillable properties for the specified object type
      */
     protected function getClassProperties(): array
     {
         $class = new $this->objectClass();
 
         $properties = $class->getFillable();
 
-        if ($this->commandPrefix == 'scalpel'
+        if (
+            $this->commandPrefix == 'scalpel'
             && in_array(\App\Traits\BelongsToTenantTrait::class, class_uses($this->objectClass))
         ) {
             $properties[] = 'tenant_id';
         }
 
         return $properties;
     }
 
     /**
      * Return object properties from the input
      */
     protected function getProperties(): array
     {
         if (is_array($this->properties)) {
             return $this->properties;
         }
 
         $this->properties = [];
 
         foreach ($this->getClassProperties() as $fillable) {
             $this->properties[$fillable] = $this->option($fillable);
         }
 
         return $this->properties;
     }
 
     /**
      * Execute the console command.
      */
     public function handle()
     {
         $object = new $this->objectClass();
 
         try {
             foreach ($this->getProperties() as $name => $value) {
                 $object->{$name} = $value;
             }
 
             $object->save();
 
             $this->info($object->{$object->getKeyName()});
         } catch (\Exception $e) {
             $this->error("Object could not be created.");
             return 1;
         }
     }
 }
diff --git a/src/app/Http/Controllers/API/V4/SearchController.php b/src/app/Http/Controllers/API/V4/SearchController.php
index 4ec70ebf..4f8f208b 100644
--- a/src/app/Http/Controllers/API/V4/SearchController.php
+++ b/src/app/Http/Controllers/API/V4/SearchController.php
@@ -1,159 +1,162 @@
 <?php
 
 namespace App\Http\Controllers\API\V4;
 
 use App\Http\Controllers\Controller;
 use App\User;
 use App\UserSetting;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\DB;
 
 /*
  * Note: We use a separate controller for search, as this will
  * be different that just a user search/listing functionality,
  * it includes aliases (and contacts), how we do the search is different too.
  */
 
 class SearchController extends Controller
 {
     /**
      * Search request for user's email addresses
      *
      * @param \Illuminate\Http\Request $request The API request.
      *
      * @return \Illuminate\Http\JsonResponse The response
      */
     public function searchSelf(Request $request)
     {
         $user = $this->guard()->user();
         $search = trim(request()->input('search'));
         $with_aliases = !empty(request()->input('alias'));
         $limit = intval(request()->input('limit'));
 
         if ($limit <= 0) {
             $limit = 15;
         } elseif ($limit > 100) {
             $limit = 100;
         }
 
         // Prepare the query
         $query = User::select('email', 'id')->where('id', $user->id);
         $aliases = DB::table('user_aliases')->select(DB::raw('alias as email, user_id as id'))
             ->where('user_id', $user->id);
 
         if (strlen($search)) {
             $aliases->whereLike('alias', $search);
             $query->whereLike('email', $search);
         }
 
         if ($with_aliases) {
             $query->union($aliases);
         }
 
         // Execute the query
         $result = $query->orderBy('email')->limit($limit)->get();
 
         $result = $this->resultFormat($result);
 
         return response()->json([
             'list' => $result,
             'count' => count($result),
         ]);
     }
 
     /**
      * Search request for addresses of all users (in an account)
      *
      * @param \Illuminate\Http\Request $request The API request.
      *
      * @return \Illuminate\Http\JsonResponse The response
      */
     public function searchUser(Request $request)
     {
         $user = $this->guard()->user();
         $search = trim(request()->input('search'));
         $with_aliases = !empty(request()->input('alias'));
         $limit = intval(request()->input('limit'));
 
         if ($limit <= 0) {
             $limit = 15;
         } elseif ($limit > 100) {
             $limit = 100;
         }
 
         $wallet = $user->wallet();
 
         // Limit users to the user's account
-        $allUsers = $wallet->entitlements()->where('entitleable_type', User::class)->select('entitleable_id')->distinct();
+        $allUsers = $wallet->entitlements()
+            ->where('entitleable_type', User::class)
+            ->select('entitleable_id')
+            ->distinct();
 
         // Sub-query for user IDs who's names match the search criteria
         $foundUserIds = UserSetting::select('user_id')
             ->whereIn('key', ['first_name', 'last_name'])
             ->whereLike('value', $search)
             ->whereIn('user_id', $allUsers);
 
         // Prepare the query
         $query = User::select('email', 'id')->whereIn('id', $allUsers);
         $aliases = DB::table('user_aliases')->select(DB::raw('alias as email, user_id as id'))
             ->whereIn('user_id', $allUsers);
 
         if (strlen($search)) {
             $query->where(function ($query) use ($foundUserIds, $search) {
                 $query->whereLike('email', $search)
                     ->orWhereIn('id', $foundUserIds);
             });
 
             $aliases->where(function ($query) use ($foundUserIds, $search) {
                 $query->whereLike('alias', $search)
                     ->orWhereIn('user_id', $foundUserIds);
             });
         }
 
         if ($with_aliases) {
             $query->union($aliases);
         }
 
         // Execute the query
         $result = $query->orderBy('email')->limit($limit)->get();
 
         $result = $this->resultFormat($result);
 
         return response()->json([
             'list' => $result,
             'count' => count($result),
         ]);
     }
 
     /**
      * Format the search result, inject user names
      */
     protected function resultFormat($result)
     {
         if ($result->count()) {
             // Get user names
             $settings = UserSetting::whereIn('key', ['first_name', 'last_name'])
                 ->whereIn('user_id', $result->pluck('id'))
                 ->get()
                 ->mapWithKeys(function ($item) {
                     return [($item->user_id . ':' . $item->key) => $item->value];
                 })
                 ->all();
 
             // "Format" the result, include user names
             $result = $result->map(function ($record) use ($settings) {
                     return [
                         'email' => $record->email,
                         'name' => trim(
                             ($settings["{$record->id}:first_name"] ?? '')
                             . ' '
                             . ($settings["{$record->id}:last_name"] ?? '')
                         ),
                     ];
                 })
                 ->sortBy(['name', 'email'])
                 ->values();
         }
 
         return $result;
     }
 }
diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php
index 080721ef..cf246138 100644
--- a/src/app/Jobs/WalletCheck.php
+++ b/src/app/Jobs/WalletCheck.php
@@ -1,293 +1,293 @@
 <?php
 
 namespace App\Jobs;
 
 use App\Http\Controllers\API\V4\PaymentsController;
 use App\Wallet;
 use Carbon\Carbon;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
 use Illuminate\Queue\InteractsWithQueue;
 
 class WalletCheck implements ShouldQueue
 {
     use Dispatchable;
     use InteractsWithQueue;
     use Queueable;
 
     public const THRESHOLD_DEGRADE = 'degrade';
     public const THRESHOLD_DEGRADE_REMINDER = 'degrade-reminder';
     public const THRESHOLD_BEFORE_DEGRADE = 'before-degrade';
     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. */
     public $backoff = 10;
 
     /** @var int How many times retry the job if it fails. */
     public $tries = 5;
 
     /** @var bool Delete the job if the wallet no longer exist. */
     public $deleteWhenMissingModels = true;
 
     /** @var ?Wallet A wallet object */
     protected $wallet;
 
     /** @var string A wallet identifier */
     protected $walletId;
 
 
     /**
      * Create a new job instance.
      *
      * @param string $walletId The wallet that has been charged.
      *
      * @return void
      */
     public function __construct(string $walletId)
     {
         $this->walletId = $walletId;
     }
 
     /**
      * Execute the job.
      *
      * @return ?string Executed action (THRESHOLD_*)
      */
     public function handle()
     {
         $this->wallet = Wallet::find($this->walletId);
 
         // Sanity check (owner deleted in meantime)
         if (!$this->wallet || !$this->wallet->owner) {
             return null;
         }
 
         if ($this->wallet->chargeEntitlements() > 0) {
-            // We make a payment when there's a charge. If for some reason the 
+            // We make a payment when there's a charge. If for some reason the
             // payment failed we can't just throw here, as another execution of this job
             // will not re-try the payment. So, we attempt a payment in a separate job.
             try {
                 $this->topUpWallet();
             } catch (\Exception $e) {
                 \Log::error("Failed to top-up wallet {$this->walletId}: " . $e->getMessage());
                 WalletCharge::dispatch($this->wallet->id);
             }
         }
 
         if ($this->wallet->balance >= 0) {
             return null;
         }
 
         $now = Carbon::now();
 
         $steps = [
             // Send the initial reminder
             self::THRESHOLD_INITIAL => 'initialReminderForDegrade',
             // Try to top-up the wallet before the second reminder
             self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet',
             // Send the second reminder
             self::THRESHOLD_REMINDER => 'secondReminderForDegrade',
             // Try to top-up the wallet before the account degradation
             self::THRESHOLD_BEFORE_DEGRADE => 'topUpWallet',
             // Degrade the account
             self::THRESHOLD_DEGRADE => 'degradeAccount',
         ];
 
         if ($this->wallet->owner && $this->wallet->owner->isDegraded()) {
             $this->degradedReminder();
             return self::THRESHOLD_DEGRADE_REMINDER;
         }
 
         foreach (array_reverse($steps, true) as $type => $method) {
             if (self::threshold($this->wallet, $type) < $now) {
                 $this->{$method}();
                 return $type;
             }
         }
 
         return null;
     }
 
     /**
      * Send the initial reminder (for the process of degrading a account)
      */
     protected function initialReminderForDegrade()
     {
         if ($this->wallet->getSetting('balance_warning_initial')) {
             return;
         }
 
         if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) {
             return;
         }
 
         if (!$this->wallet->owner->isSuspended()) {
             $this->sendMail(\App\Mail\NegativeBalance::class, false);
         }
 
         $now = \Carbon\Carbon::now()->toDateTimeString();
         $this->wallet->setSetting('balance_warning_initial', $now);
     }
 
     /**
      * Send the second reminder (for the process of degrading a account)
      */
     protected function secondReminderForDegrade()
     {
         if ($this->wallet->getSetting('balance_warning_reminder')) {
             return;
         }
 
         if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) {
             return;
         }
 
         if (!$this->wallet->owner->isSuspended()) {
             $this->sendMail(\App\Mail\NegativeBalanceReminderDegrade::class, true);
         }
 
         $now = \Carbon\Carbon::now()->toDateTimeString();
         $this->wallet->setSetting('balance_warning_reminder', $now);
     }
 
     /**
      * Degrade the account
      */
     protected function degradeAccount()
     {
         // The account may be already deleted, or degraded
         if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) {
             return;
         }
 
         $email = $this->wallet->owner->email;
 
         // The dirty work will be done by UserObserver
         $this->wallet->owner->degrade();
 
         \Log::info(
             sprintf(
                 "[WalletCheck] Account degraded %s (%s)",
                 $this->wallet->id,
                 $email
             )
         );
 
         if (!$this->wallet->owner->isSuspended()) {
             $this->sendMail(\App\Mail\NegativeBalanceDegraded::class, true);
         }
     }
 
     /**
      * Send the periodic reminder to the degraded account owners
      */
     protected function degradedReminder()
     {
         // Sanity check
         if (!$this->wallet->owner || !$this->wallet->owner->isDegraded()) {
             return;
         }
 
         if ($this->wallet->owner->isSuspended()) {
             return;
         }
 
         $now = \Carbon\Carbon::now();
         $last = $this->wallet->getSetting('degraded_last_reminder');
 
         if ($last) {
             $last = new Carbon($last);
             $period = 14;
 
             if ($last->addDays($period) > $now) {
                 return;
             }
 
             $this->sendMail(\App\Mail\DegradedAccountReminder::class, false);
         }
 
         $this->wallet->setSetting('degraded_last_reminder', $now->toDateTimeString());
     }
 
     /**
      * Send the email
      *
      * @param string  $class         Mailable class name
      * @param bool    $with_external Use users's external email
      */
     protected function sendMail($class, $with_external = false): void
     {
         // TODO: Send the email to all wallet controllers?
 
         $mail = new $class($this->wallet, $this->wallet->owner);
 
         list($to, $cc) = \App\Mail\Helper::userEmails($this->wallet->owner, $with_external);
 
         if (!empty($to) || !empty($cc)) {
             $params = [
                 'to' => $to,
                 'cc' => $cc,
                 'add' => " for {$this->wallet->id}",
             ];
 
             \App\Mail\Helper::sendMail($mail, $this->wallet->owner->tenant_id, $params);
         }
     }
 
     /**
      * Get the date-time for an action threshold. Calculated using
      * the date when a wallet balance turned negative.
      *
      * @param \App\Wallet $wallet A wallet
      * @param string      $type   Action type (one of self::THRESHOLD_*)
      *
      * @return \Carbon\Carbon The threshold date-time object
      */
     public static function threshold(Wallet $wallet, string $type): ?Carbon
     {
         $negative_since = $wallet->getSetting('balance_negative_since');
 
         // Migration scenario: balance<0, but no balance_negative_since set
         if (!$negative_since) {
             // 2h back from now, so first run can sent the initial notification
             $negative_since = Carbon::now()->subHours(2);
             $wallet->setSetting('balance_negative_since', $negative_since->toDateTimeString());
         } else {
             $negative_since = new Carbon($negative_since);
         }
 
         // Initial notification
         // Give it an hour so the async recurring payment has a chance to be finished
         if ($type == self::THRESHOLD_INITIAL) {
             return $negative_since->addHours(1);
         }
 
         $thresholds = [
             // A day before the second reminder
             self::THRESHOLD_BEFORE_REMINDER => 7 - 1,
             // Second notification
             self::THRESHOLD_REMINDER => 7,
             // Last chance to top-up the wallet
             self::THRESHOLD_BEFORE_DEGRADE => 13,
             // Account degradation
             self::THRESHOLD_DEGRADE => 14,
         ];
 
         if (!empty($thresholds[$type])) {
             return $negative_since->addDays($thresholds[$type]);
         }
 
         return null;
     }
 
     /**
      * Try to automatically top-up the wallet
      */
     protected function topUpWallet(): void
     {
         PaymentsController::topUpWallet($this->wallet);
     }
 }
diff --git a/src/database/migrations/2020_05_05_095212_create_tenants_table.php b/src/database/migrations/2020_05_05_095212_create_tenants_table.php
index 4903c140..fdc4275e 100644
--- a/src/database/migrations/2020_05_05_095212_create_tenants_table.php
+++ b/src/database/migrations/2020_05_05_095212_create_tenants_table.php
@@ -1,78 +1,75 @@
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
 // phpcs:ignore
 class CreateTenantsTable extends Migration
 {
     /**
      * Run the migrations.
      *
      * @return void
      */
     public function up()
     {
         Schema::create(
             'tenants',
             function (Blueprint $table) {
                 $table->bigIncrements('id');
                 $table->string('title', 32);
                 $table->timestamps();
             }
         );
 
-
-
         foreach (['users', 'discounts', 'domains', 'plans', 'packages', 'skus'] as $tableName) {
             Schema::table(
                 $tableName,
                 function (Blueprint $table) {
                     $table->bigInteger('tenant_id')->unsigned()->nullable();
                     $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('set null');
                 }
             );
-
         }
 
         // Add fee column
         foreach (['entitlements', 'skus'] as $table) {
             Schema::table(
                 $table,
                 function (Blueprint $table) {
                     $table->integer('fee')->nullable();
                 }
             );
         }
     }
 
     /**
      * Reverse the migrations.
      *
      * @return void
      */
     public function down()
     {
         foreach (['users', 'discounts', 'domains', 'plans', 'packages', 'skus'] as $tableName) {
             Schema::table(
                 $tableName,
                 function (Blueprint $table) {
                     $table->dropForeign(['tenant_id']);
                     $table->dropColumn('tenant_id');
                 }
             );
         }
 
         foreach (['entitlements', 'skus'] as $table) {
             Schema::table(
                 $table,
                 function (Blueprint $table) {
                     $table->dropColumn('fee');
                 }
             );
         }
 
         Schema::dropIfExists('tenants');
     }
 }
diff --git a/src/tests/Feature/Console/Scalpel/Domain/CreateCommandTest.php b/src/tests/Feature/Console/Scalpel/Domain/CreateCommandTest.php
index 8a7bdf07..dbe03d99 100644
--- a/src/tests/Feature/Console/Scalpel/Domain/CreateCommandTest.php
+++ b/src/tests/Feature/Console/Scalpel/Domain/CreateCommandTest.php
@@ -1,64 +1,64 @@
 <?php
 
 namespace Tests\Feature\Console\Scalpel\Domain;
 
 use App\Domain;
 use Tests\TestCase;
 
 class CreateCommandTest extends TestCase
 {
     /**
      * {@inheritDoc}
      */
     public function setUp(): void
     {
         parent::setUp();
 
         $this->deleteTestDomain('domain-delete.com');
     }
 
     /**
      * {@inheritDoc}
      */
     public function tearDown(): void
     {
         $this->deleteTestDomain('domain-delete.com');
 
         parent::tearDown();
     }
 
     /**
      * Test the command execution
      */
     public function testHandle(): void
     {
         // Test --help argument
         $code = \Artisan::call("scalpel:domain:create --help");
         $output = trim(\Artisan::output());
 
         $this->assertSame(0, $code);
         $this->assertStringContainsString('--namespace[=NAMESPACE]', $output);
         $this->assertStringContainsString('--type[=TYPE]', $output);
         $this->assertStringContainsString('--status[=STATUS]', $output);
         $this->assertStringContainsString('--tenant_id[=TENANT_ID]', $output);
 
         $tenant = \App\Tenant::orderBy('id', 'desc')->first();
 
         // Test successful domain creation
         $code = \Artisan::call("scalpel:domain:create"
             . " --namespace=domain-delete.com"
             . " --type=" . Domain::TYPE_PUBLIC
             . " --tenant_id={$tenant->id}"
-            );
+        );
 
         $output = trim(\Artisan::output());
 
         $domain = $this->getTestDomain('domain-delete.com');
 
         $this->assertSame(0, $code);
         $this->assertSame($output, (string) $domain->id);
         $this->assertSame('domain-delete.com', $domain->namespace);
         $this->assertSame(Domain::TYPE_PUBLIC, $domain->type);
         $this->assertSame($domain->tenant_id, $tenant->id);
     }
 }
diff --git a/src/tests/Feature/Controller/WalletsTest.php b/src/tests/Feature/Controller/WalletsTest.php
index 6b1e1559..8a30bd89 100644
--- a/src/tests/Feature/Controller/WalletsTest.php
+++ b/src/tests/Feature/Controller/WalletsTest.php
@@ -1,356 +1,357 @@
 <?php
 
 namespace Tests\Feature\Controller;
 
 use App\Http\Controllers\API\V4\WalletsController;
 use App\Payment;
 use App\Transaction;
 use Carbon\Carbon;
 use Tests\TestCase;
 
 class WalletsTest extends TestCase
 {
     /**
      * {@inheritDoc}
      */
     public function setUp(): void
     {
         parent::setUp();
 
         $this->deleteTestUser('wallets-controller@kolabnow.com');
     }
 
     /**
      * {@inheritDoc}
      */
     public function tearDown(): void
     {
         $this->deleteTestUser('wallets-controller@kolabnow.com');
 
         parent::tearDown();
     }
 
     /**
      * Test for getWalletNotice() method
      */
     public function testGetWalletNotice(): void
     {
         $user = $this->getTestUser('wallets-controller@kolabnow.com');
         $plan = \App\Plan::withObjectTenantContext($user)->where('title', 'individual')->first();
         $user->assignPlan($plan);
         $wallet = $user->wallets()->first();
 
         $controller = new WalletsController();
         $method = new \ReflectionMethod($controller, 'getWalletNotice');
         $method->setAccessible(true);
 
         // User/entitlements created today, balance=0
         $notice = $method->invoke($controller, $wallet);
 
         $this->assertSame('You are in your free trial period.', $notice);
 
         $wallet->owner->created_at = Carbon::now()->subWeeks(3);
         $wallet->owner->save();
 
         $notice = $method->invoke($controller, $wallet);
 
         $this->assertSame('Your free trial is about to end, top up to continue.', $notice);
 
         // User/entitlements created today, balance=-10 CHF
         $wallet->balance = -1000;
         $notice = $method->invoke($controller, $wallet);
 
         $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)
         $this->backdateEntitlements($wallet->entitlements, Carbon::now()->subMonthsWithoutOverflow(1)->subDays(1));
         $wallet->refresh();
 
         // test "1 month"
         $wallet->balance = 990;
         $notice = $method->invoke($controller, $wallet);
 
         $this->assertMatchesRegularExpression('/\((1 month|4 weeks)\)/', $notice);
 
         // test "2 months"
         $wallet->balance = 990 * 2.6;
         $notice = $method->invoke($controller, $wallet);
 
         $this->assertMatchesRegularExpression('/\(1 month 4 weeks\)/', $notice);
 
         // Change locale to make sure the text is localized by Carbon
         \app()->setLocale('de');
 
         // test "almost 2 years"
         $wallet->balance = 990 * 23.5;
         $notice = $method->invoke($controller, $wallet);
 
         $this->assertMatchesRegularExpression('/\(1 Jahr 10 Monate\)/', $notice);
 
         // Old entitlements, 100% discount
         $this->backdateEntitlements($wallet->entitlements, Carbon::now()->subDays(40));
         $discount = \App\Discount::withObjectTenantContext($user)->where('discount', 100)->first();
         $wallet->discount()->associate($discount);
 
         $notice = $method->invoke($controller, $wallet->refresh());
 
         $this->assertSame(null, $notice);
     }
 
     /**
      * Test fetching pdf receipt
      */
     public function testReceiptDownload(): void
     {
         $user = $this->getTestUser('wallets-controller@kolabnow.com');
         $john = $this->getTestUser('john@kolab.org');
         $wallet = $user->wallets()->first();
 
         // Unauth access not allowed
         $response = $this->get("api/v4/wallets/{$wallet->id}/receipts/2020-05");
         $response->assertStatus(401);
         $response = $this->actingAs($john)->get("api/v4/wallets/{$wallet->id}/receipts/2020-05");
         $response->assertStatus(403);
 
         // Invalid receipt id (current month)
         $receiptId = date('Y-m');
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/receipts/{$receiptId}");
         $response->assertStatus(404);
 
         // Invalid receipt id
         $receiptId = '1000-03';
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/receipts/{$receiptId}");
         $response->assertStatus(404);
 
         // Valid receipt id
         $year = intval(date('Y')) - 1;
         $receiptId = "$year-12";
         $filename = \config('app.name') . " Receipt for $year-12";
 
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/receipts/{$receiptId}");
 
         $response->assertStatus(200);
         $response->assertHeader('content-type', 'application/pdf');
         $response->assertHeader('content-disposition', 'attachment; filename="' . $filename . '"');
         $response->assertHeader('content-length');
 
         $length = $response->headers->get('content-length');
         $content = $response->content();
         $this->assertStringStartsWith("%PDF-1.", $content);
         $this->assertEquals(strlen($content), $length);
     }
 
     /**
      * Test fetching list of receipts
      */
     public function testReceipts(): void
     {
         $user = $this->getTestUser('wallets-controller@kolabnow.com');
         $john = $this->getTestUser('john@kolab.org');
         $wallet = $user->wallets()->first();
         $wallet->payments()->delete();
 
         // Unauth access not allowed
         $response = $this->get("api/v4/wallets/{$wallet->id}/receipts");
         $response->assertStatus(401);
         $response = $this->actingAs($john)->get("api/v4/wallets/{$wallet->id}/receipts");
         $response->assertStatus(403);
 
         // Empty list expected
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/receipts");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame([], $json['list']);
         $this->assertSame(1, $json['page']);
         $this->assertSame(0, $json['count']);
         $this->assertSame(false, $json['hasMore']);
 
         // Insert a payment to the database
         $date = Carbon::create(intval(date('Y')) - 1, 4, 30);
         $payment = Payment::create([
                 'id' => 'AAA1',
                 'status' => Payment::STATUS_PAID,
                 'type' => Payment::TYPE_ONEOFF,
                 'description' => 'Paid in April',
                 'wallet_id' => $wallet->id,
                 'provider' => 'stripe',
                 'amount' => 1111,
                 'credit_amount' => 1111,
                 'currency' => 'CHF',
                 'currency_amount' => 1111,
         ]);
         $payment->updated_at = $date;
         $payment->save();
 
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/receipts");
         $response->assertStatus(200);
 
         $json = $response->json();
 
+        $expected = ['period' => $date->format('Y-m'), 'amount' => '1111', 'currency' => 'CHF'];
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
-        $this->assertSame(['period' => $date->format('Y-m'), 'amount' => '1111', 'currency' => 'CHF'], $json['list'][0]);
+        $this->assertSame($expected, $json['list'][0]);
         $this->assertSame(1, $json['page']);
         $this->assertSame(1, $json['count']);
         $this->assertSame(false, $json['hasMore']);
     }
 
     /**
      * Test fetching a wallet (GET /api/v4/wallets/:id)
      */
     public function testShow(): void
     {
         $john = $this->getTestUser('john@kolab.org');
         $jack = $this->getTestUser('jack@kolab.org');
         $wallet = $john->wallets()->first();
         $wallet->balance = -100;
         $wallet->save();
 
         // Accessing a wallet of someone else
         $response = $this->actingAs($jack)->get("api/v4/wallets/{$wallet->id}");
         $response->assertStatus(403);
 
         // Accessing non-existing wallet
         $response = $this->actingAs($jack)->get("api/v4/wallets/aaa");
         $response->assertStatus(404);
 
         // Wallet owner
         $response = $this->actingAs($john)->get("api/v4/wallets/{$wallet->id}");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertSame($wallet->id, $json['id']);
         $this->assertSame('CHF', $json['currency']);
         $this->assertSame($wallet->balance, $json['balance']);
         $this->assertTrue(empty($json['description']));
         $this->assertTrue(!empty($json['notice']));
     }
 
     /**
      * Test fetching wallet transactions
      */
     public function testTransactions(): void
     {
         $package_kolab = \App\Package::where('title', 'kolab')->first();
         $user = $this->getTestUser('wallets-controller@kolabnow.com');
         $user->assignPackage($package_kolab);
         $john = $this->getTestUser('john@kolab.org');
         $wallet = $user->wallets()->first();
 
         // Unauth access not allowed
         $response = $this->get("api/v4/wallets/{$wallet->id}/transactions");
         $response->assertStatus(401);
         $response = $this->actingAs($john)->get("api/v4/wallets/{$wallet->id}/transactions");
         $response->assertStatus(403);
 
         // Expect empty list
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame([], $json['list']);
         $this->assertSame(1, $json['page']);
         $this->assertSame(0, $json['count']);
         $this->assertSame(false, $json['hasMore']);
 
         // Create some sample transactions
         $transactions = $this->createTestTransactions($wallet);
         $transactions = array_reverse($transactions);
         $pages = array_chunk($transactions, 10 /* page size*/);
 
         // Get the first page
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame(1, $json['page']);
         $this->assertSame(10, $json['count']);
         $this->assertSame(true, $json['hasMore']);
         $this->assertCount(10, $json['list']);
         foreach ($pages[0] as $idx => $transaction) {
             $this->assertSame($transaction->id, $json['list'][$idx]['id']);
             $this->assertSame($transaction->type, $json['list'][$idx]['type']);
             $this->assertSame(\config('app.currency'), $json['list'][$idx]['currency']);
             $this->assertSame($transaction->shortDescription(), $json['list'][$idx]['description']);
             $this->assertFalse($json['list'][$idx]['hasDetails']);
             $this->assertFalse(array_key_exists('user', $json['list'][$idx]));
         }
 
         $search = null;
 
         // Get the second page
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions?page=2");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame(2, $json['page']);
         $this->assertSame(2, $json['count']);
         $this->assertSame(false, $json['hasMore']);
         $this->assertCount(2, $json['list']);
         foreach ($pages[1] as $idx => $transaction) {
             $this->assertSame($transaction->id, $json['list'][$idx]['id']);
             $this->assertSame($transaction->type, $json['list'][$idx]['type']);
             $this->assertSame($transaction->shortDescription(), $json['list'][$idx]['description']);
             $this->assertSame(
                 $transaction->type == Transaction::WALLET_DEBIT,
                 $json['list'][$idx]['hasDetails']
             );
             $this->assertFalse(array_key_exists('user', $json['list'][$idx]));
 
             if ($transaction->type == Transaction::WALLET_DEBIT) {
                 $search = $transaction->id;
             }
         }
 
         // Get a non-existing page
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions?page=3");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame(3, $json['page']);
         $this->assertSame(0, $json['count']);
         $this->assertSame(false, $json['hasMore']);
         $this->assertCount(0, $json['list']);
 
         // Sub-transaction searching
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions?transaction=123");
         $response->assertStatus(404);
 
         $response = $this->actingAs($user)->get("api/v4/wallets/{$wallet->id}/transactions?transaction={$search}");
         $response->assertStatus(200);
 
         $json = $response->json();
 
         $this->assertCount(5, $json);
         $this->assertSame('success', $json['status']);
         $this->assertSame(1, $json['page']);
         $this->assertSame(2, $json['count']);
         $this->assertSame(false, $json['hasMore']);
         $this->assertCount(2, $json['list']);
         $this->assertSame(Transaction::ENTITLEMENT_BILLED, $json['list'][0]['type']);
         $this->assertSame(Transaction::ENTITLEMENT_BILLED, $json['list'][1]['type']);
 
         // Test that John gets 404 if he tries to access
         // someone else's transaction ID on his wallet's endpoint
         $wallet = $john->wallets()->first();
         $response = $this->actingAs($john)->get("api/v4/wallets/{$wallet->id}/transactions?transaction={$search}");
         $response->assertStatus(404);
     }
 }