php-commerce/View/Modal/Admin/ReferralManager.php
2026-01-27 00:24:22 +00:00

415 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Commerce\View\Modal\Admin;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithPagination;
use Core\Commerce\Models\Referral;
use Core\Commerce\Models\ReferralCode;
use Core\Commerce\Models\ReferralCommission;
use Core\Commerce\Models\ReferralPayout;
use Core\Commerce\Services\ReferralService;
/**
* Admin dashboard for managing referrals, commissions, and payouts.
*/
#[Layout('hub::admin.layouts.app')]
#[Title('Referrals')]
class ReferralManager extends Component
{
use WithPagination;
// Active tab
public string $tab = 'referrals';
// Filters
public string $search = '';
public string $statusFilter = '';
// Referral modal
public bool $showReferralModal = false;
public ?int $viewingReferralId = null;
// Payout modal
public bool $showPayoutModal = false;
public ?int $processingPayoutId = null;
public string $payoutBtcTxid = '';
public ?float $payoutBtcAmount = null;
public ?float $payoutBtcRate = null;
public string $payoutFailReason = '';
// Code modal
public bool $showCodeModal = false;
public ?int $editingCodeId = null;
public string $codeCode = '';
public ?int $codeUserId = null;
public string $codeType = 'custom';
public ?float $codeCommissionRate = null;
public int $codeCookieDays = 90;
public ?int $codeMaxUses = null;
public ?string $codeValidFrom = null;
public ?string $codeValidUntil = null;
public bool $codeIsActive = true;
public ?string $codeCampaignName = null;
public function mount(): void
{
if (! auth()->user()?->isHades()) {
abort(403, 'Hades tier required for referral management.');
}
}
public function updatingSearch(): void
{
$this->resetPage();
}
public function updatingStatusFilter(): void
{
$this->resetPage();
}
public function switchTab(string $tab): void
{
$this->tab = $tab;
$this->resetPage();
$this->search = '';
$this->statusFilter = '';
}
// ─────────────────────────────────────────────────────────────────────────
// Referrals
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function referrals()
{
return Referral::with(['referrer', 'referee'])
->when($this->search, function ($query) {
$query->whereHas('referrer', fn ($q) => $q->where('email', 'like', "%{$this->search}%"))
->orWhereHas('referee', fn ($q) => $q->where('email', 'like', "%{$this->search}%"))
->orWhere('code', 'like', "%{$this->search}%");
})
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
->latest()
->paginate(25);
}
public function viewReferral(int $id): void
{
$this->viewingReferralId = $id;
$this->showReferralModal = true;
}
public function disqualifyReferral(int $id, ReferralService $referralService): void
{
$referral = Referral::findOrFail($id);
$referralService->disqualifyReferral($referral, 'Manually disqualified by admin');
session()->flash('message', 'Referral disqualified.');
$this->showReferralModal = false;
}
public function closeReferralModal(): void
{
$this->showReferralModal = false;
$this->viewingReferralId = null;
}
#[Computed]
public function viewingReferral()
{
if (! $this->viewingReferralId) {
return null;
}
return Referral::with(['referrer', 'referee', 'commissions'])
->find($this->viewingReferralId);
}
// ─────────────────────────────────────────────────────────────────────────
// Commissions
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function commissions()
{
return ReferralCommission::with(['referrer', 'referral.referee', 'order'])
->when($this->search, function ($query) {
$query->whereHas('referrer', fn ($q) => $q->where('email', 'like', "%{$this->search}%"));
})
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
->latest()
->paginate(25);
}
public function matureCommissions(ReferralService $referralService): void
{
$count = $referralService->matureReadyCommissions();
session()->flash('message', "{$count} commissions matured.");
}
// ─────────────────────────────────────────────────────────────────────────
// Payouts
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function payouts()
{
return ReferralPayout::with(['user', 'processor'])
->when($this->search, function ($query) {
$query->whereHas('user', fn ($q) => $q->where('email', 'like', "%{$this->search}%"))
->orWhere('payout_number', 'like', "%{$this->search}%");
})
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
->latest()
->paginate(25);
}
public function openProcessPayout(int $id): void
{
$this->processingPayoutId = $id;
$this->payoutBtcTxid = '';
$this->payoutBtcAmount = null;
$this->payoutBtcRate = null;
$this->payoutFailReason = '';
$this->showPayoutModal = true;
}
public function processPayout(ReferralService $referralService): void
{
$payout = ReferralPayout::findOrFail($this->processingPayoutId);
$referralService->processPayout($payout, auth()->user());
session()->flash('message', 'Payout marked as processing.');
}
public function completePayout(ReferralService $referralService): void
{
$payout = ReferralPayout::findOrFail($this->processingPayoutId);
$referralService->completePayout(
$payout,
$this->payoutBtcTxid ?: null,
$this->payoutBtcAmount,
$this->payoutBtcRate
);
session()->flash('message', 'Payout completed.');
$this->closePayoutModal();
}
public function failPayout(ReferralService $referralService): void
{
if (! $this->payoutFailReason) {
session()->flash('error', 'Please provide a failure reason.');
return;
}
$payout = ReferralPayout::findOrFail($this->processingPayoutId);
$referralService->failPayout($payout, $this->payoutFailReason);
session()->flash('message', 'Payout marked as failed.');
$this->closePayoutModal();
}
public function closePayoutModal(): void
{
$this->showPayoutModal = false;
$this->processingPayoutId = null;
}
#[Computed]
public function processingPayout()
{
if (! $this->processingPayoutId) {
return null;
}
return ReferralPayout::with(['user', 'commissions'])->find($this->processingPayoutId);
}
// ─────────────────────────────────────────────────────────────────────────
// Referral Codes
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function codes()
{
return ReferralCode::with('user')
->when($this->search, function ($query) {
$query->where('code', 'like', "%{$this->search}%")
->orWhere('campaign_name', 'like', "%{$this->search}%");
})
->when($this->statusFilter === 'active', fn ($q) => $q->where('is_active', true))
->when($this->statusFilter === 'inactive', fn ($q) => $q->where('is_active', false))
->latest()
->paginate(25);
}
public function openCreateCode(): void
{
$this->resetCodeForm();
$this->codeCode = strtoupper(substr(md5(uniqid()), 0, 8));
$this->showCodeModal = true;
}
public function openEditCode(int $id): void
{
$code = ReferralCode::findOrFail($id);
$this->editingCodeId = $id;
$this->codeCode = $code->code;
$this->codeUserId = $code->user_id;
$this->codeType = $code->type;
$this->codeCommissionRate = $code->commission_rate;
$this->codeCookieDays = $code->cookie_days;
$this->codeMaxUses = $code->max_uses;
$this->codeValidFrom = $code->valid_from?->format('Y-m-d');
$this->codeValidUntil = $code->valid_until?->format('Y-m-d');
$this->codeIsActive = $code->is_active;
$this->codeCampaignName = $code->campaign_name;
$this->showCodeModal = true;
}
public function saveCode(): void
{
$this->validate([
'codeCode' => ['required', 'string', 'max:64', 'regex:/^[A-Z0-9_-]+$/'],
'codeType' => ['required', 'in:user,campaign,custom'],
'codeCommissionRate' => ['nullable', 'numeric', 'min:0', 'max:100'],
'codeCookieDays' => ['required', 'integer', 'min:1', 'max:365'],
'codeMaxUses' => ['nullable', 'integer', 'min:1'],
'codeValidFrom' => ['nullable', 'date'],
'codeValidUntil' => ['nullable', 'date', 'after_or_equal:codeValidFrom'],
]);
$data = [
'code' => strtoupper($this->codeCode),
'user_id' => $this->codeUserId,
'type' => $this->codeType,
'commission_rate' => $this->codeCommissionRate,
'cookie_days' => $this->codeCookieDays,
'max_uses' => $this->codeMaxUses,
'valid_from' => $this->codeValidFrom ? \Carbon\Carbon::parse($this->codeValidFrom) : null,
'valid_until' => $this->codeValidUntil ? \Carbon\Carbon::parse($this->codeValidUntil) : null,
'is_active' => $this->codeIsActive,
'campaign_name' => $this->codeCampaignName,
];
if ($this->editingCodeId) {
ReferralCode::findOrFail($this->editingCodeId)->update($data);
session()->flash('message', 'Referral code updated.');
} else {
ReferralCode::create($data);
session()->flash('message', 'Referral code created.');
}
$this->closeCodeModal();
}
public function toggleCodeActive(int $id): void
{
$code = ReferralCode::findOrFail($id);
$code->update(['is_active' => ! $code->is_active]);
session()->flash('message', $code->is_active ? 'Code activated.' : 'Code deactivated.');
}
public function deleteCode(int $id): void
{
$code = ReferralCode::findOrFail($id);
if ($code->uses_count > 0) {
session()->flash('error', 'Cannot delete code that has been used.');
return;
}
$code->delete();
session()->flash('message', 'Referral code deleted.');
}
public function closeCodeModal(): void
{
$this->showCodeModal = false;
$this->resetCodeForm();
}
protected function resetCodeForm(): void
{
$this->editingCodeId = null;
$this->codeCode = '';
$this->codeUserId = null;
$this->codeType = 'custom';
$this->codeCommissionRate = null;
$this->codeCookieDays = 90;
$this->codeMaxUses = null;
$this->codeValidFrom = null;
$this->codeValidUntil = null;
$this->codeIsActive = true;
$this->codeCampaignName = null;
}
// ─────────────────────────────────────────────────────────────────────────
// Statistics
// ─────────────────────────────────────────────────────────────────────────
#[Computed]
public function stats()
{
return app(ReferralService::class)->getGlobalStats();
}
#[Computed]
public function statusOptions(): array
{
return match ($this->tab) {
'referrals' => [
'pending' => 'Pending',
'converted' => 'Converted',
'qualified' => 'Qualified',
'disqualified' => 'Disqualified',
],
'commissions' => [
'pending' => 'Pending',
'matured' => 'Matured',
'paid' => 'Paid',
'cancelled' => 'Cancelled',
],
'payouts' => [
'requested' => 'Requested',
'processing' => 'Processing',
'completed' => 'Completed',
'failed' => 'Failed',
'cancelled' => 'Cancelled',
],
'codes' => [
'active' => 'Active',
'inactive' => 'Inactive',
],
default => [],
};
}
public function render()
{
return view('commerce::admin.referral-manager')
->layout('hub::admin.layouts.app', ['title' => 'Referrals']);
}
}