where('email', $user->email)->first(); // Somebody already on a contract is not buying a second cloud from // this page — changing package is a plan change, which prorates and // keeps their data where it is. $contract = $customer === null ? null : Subscription::query() ->where('customer_id', $customer->id) ->whereIn('status', ['active', 'past_due']) ->first(); return view('livewire.order', [ 'plans' => $this->plans(), 'contract' => $contract, 'vat' => rtrim(rtrim(number_format(CompanyProfile::taxRate(), 1, ',', '.'), '0'), ','), 'setup' => CompanyProfile::setupFeeCents(), ]); } /** * The packages on sale, in the words and figures the public sheet uses. * * @return array> */ private function plans(): array { try { $sellable = app(PlanCatalogue::class)->sellable(); } catch (Throwable) { // The catalogue fails loudly by design — an overlap must not decide // a customer's terms by row order. A portal page is not the place // to argue about it: no list, one sentence, and the operator's // console is already shouting. return []; } $capacity = app(HostCapacity::class); $rate = CompanyProfile::taxRate(); $plans = []; foreach ($sellable as $key => $plan) { $net = (int) $plan['price_cents']; $plans[] = [ 'key' => $key, 'name' => (string) $plan['name'], 'audience' => (string) ($plan['audience'] ?? ''), 'gross' => (int) round($net * (1 + $rate / 100)), 'net' => $net, 'currency' => (string) $plan['currency'], 'quota_gb' => (int) $plan['quota_gb'], 'traffic_gb' => (int) $plan['traffic_gb'], 'seats' => (int) $plan['seats'], 'recommended' => (bool) ($plan['recommended'] ?? false), 'delivery' => $capacity->deliveryFor((int) ($plan['disk_gb'] ?? 0)), ]; } return $plans; } }