108 lines
4.0 KiB
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),
|
|
]);
|
|
}
|
|
}
|