CluPilotCloud/app/Livewire/Admin/RecordWithdrawal.php

108 lines
4.0 KiB
PHP

<?php
namespace App\Livewire\Admin;
use App\Actions\WithdrawContract;
use App\Models\Customer;
use App\Models\Subscription;
use App\Services\Billing\CustomDomainAccess;
use App\Services\Billing\WithdrawalRight;
use LivewireUI\Modal\ModalComponent;
use RuntimeException;
/**
* A withdrawal that arrived by telephone or by post, typed in by an operator.
*
* The law does not require a consumer to use our form. A sentence on the phone,
* a letter, an email — any unambiguous declaration inside the fourteen days
* ends the contract, and if the only way to record one were the portal button,
* the ones that came in every other way would be handled by hand in Stripe and
* never appear in the register at all.
*
* So it goes through the SAME action as the portal, with the channel recorded
* and the operator's name on it. Nothing is decided here that is not decided
* there: a business customer is refused, an expired window is refused, and the
* refund is the whole amount either way. An operator cannot override any of it,
* which is the point — a withdrawal an operator could approve out of the window
* would be a refund with no legal basis and no document that could justify it.
*
* `customers.manage` rather than `customers.grant_plan`: this is not a gift an
* operator decides to make, it is a declaration the customer has already made
* and somebody is writing down. Support answers the telephone, and Support holds
* `customers.manage`.
*/
class RecordWithdrawal extends ModalComponent
{
public string $customerUuid = '';
public string $customerName = '';
/** Why it cannot be recorded, in the customer's own words. Null when it can. */
public ?string $refusal = null;
public function mount(string $uuid): void
{
// Modals are reachable without the route middleware, so the permission
// is checked here as well as on the page that opens this one.
$this->authorize('customers.manage');
$customer = Customer::query()->where('uuid', $uuid)->firstOrFail();
$this->customerUuid = $uuid;
$this->customerName = (string) $customer->name;
$this->refusal = WithdrawalRight::for($this->contractOf($customer))->refusal;
}
public function record(): void
{
$this->authorize('customers.manage');
$customer = Customer::query()->where('uuid', $this->customerUuid)->firstOrFail();
$contract = $this->contractOf($customer);
if ($contract === null) {
$this->refusal = __('withdrawal.refusal_no_contract');
return;
}
try {
app(WithdrawContract::class)(
$contract,
WithdrawContract::CHANNEL_OPERATOR,
// Who took it. Part of the evidence that a declaration was made
// at all — a refund with nobody's name against it is the one an
// auditor asks about.
auth('operator')->user(),
);
} catch (RuntimeException $e) {
$this->refusal = $e->getMessage();
return;
}
$this->dispatch('notify', message: __('withdrawal.recorded', ['name' => $this->customerName]));
$this->closeModal();
}
private function contractOf(Customer $customer): ?Subscription
{
return app(CustomDomainAccess::class)->contractOf($customer);
}
public function render()
{
$customer = Customer::query()->where('uuid', $this->customerUuid)->first();
$contract = $customer === null ? null : $this->contractOf($customer);
return view('livewire.admin.record-withdrawal', [
'right' => WithdrawalRight::for($contract),
// How long the cloud has been running. Nothing is charged for it —
// the refund is the whole amount — but it is the first thing an
// operator is asked on the telephone.
'deliveredDays' => $contract === null ? 0 : WithdrawalRight::deliveredDays($contract),
'termDays' => $contract === null ? 0 : WithdrawalRight::termDays($contract),
]);
}
}