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(); // Resolved once and used for every figure on the page, so the package // price, the setup fee and the sentence about VAT underneath them cannot // state three different treatments of one sale. It is the same call the // checkout makes to pick the Stripe Price, so what is shown here is what // will be taken. $tax = TaxTreatment::for($customer); return view('livewire.order', [ 'plans' => $this->plans($tax), 'tax' => $tax, 'contract' => $contract, 'vat' => rtrim(rtrim(number_format(CompanyProfile::taxRate(), 1, ',', '.'), '0'), ','), // At the till, like the package price above it and like the public // sheet, which quotes "Einrichtung kostet einmalig 118,80 €". This page // was quoting the NET figure under the same sentence, so the site said // 118,80 and the page with the button on it said 99,00. Through the // same treatment as the package, because a fee is a supply like any // other and cannot be taxed differently from the thing it sets up. 'setup' => SetupFee::chargedCents($tax), ]); } /** * The packages on sale, in the words and figures the public sheet uses — * priced for whoever is reading them. * * `gross` is what this customer's card would be charged, formed by * TaxTreatment and not by a multiplication of its own. It used to be the * latter, which is a second source for a number the checkout also forms, and * therefore a second answer waiting to happen. * * @return array> */ private function plans(TaxTreatment $tax): 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); $plans = []; foreach ($sellable as $key => $plan) { $net = (int) $plan['price_cents']; $plans[] = [ 'key' => $key, 'name' => (string) $plan['name'], 'audience' => (string) ($plan['audience'] ?? ''), 'gross' => $tax->chargeCents($net), '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; } }