@extends('layouts.admin-modern') @section('title', 'Pilotage des paiements') @php $money = fn ($value) => number_format((float) ($value ?? 0), 2, ',', ' ') . ' EUR'; $statusBadge = function (?string $status): string { return match (strtolower((string) $status)) { 'paid', 'completed', 'released', 'validated', 'returned' => 'bg-green-100 text-green-800 border border-green-200', 'pending', 'processing', 'held', 'pending_capture', 'under_review' => 'bg-amber-100 text-amber-800 border border-amber-200', 'refunded', 'partially_refunded', 'partial', 'partial_refund' => 'bg-sky-100 text-sky-800 border border-sky-200', 'failed', 'cancelled', 'rejected', 'retained' => 'bg-red-100 text-red-800 border border-red-200', 'disputed', 'open' => 'bg-violet-100 text-violet-800 border border-violet-200', default => 'bg-slate-100 text-slate-800 border border-slate-200', }; }; $statusLabel = function (?string $status): string { return match (strtolower((string) $status)) { 'paid' => 'Paye', 'completed' => 'Complete', 'released' => 'Libere', 'pending' => 'En attente', 'processing' => 'Traitement', 'held' => 'Bloque', 'pending_capture' => 'Autorise', 'refunded' => 'Rembourse', 'partially_refunded' => 'Rembourse partiel', 'partial_refund' => 'Rembourse partiel', 'partial' => 'Partiel', 'failed' => 'Echec', 'cancelled' => 'Annule', 'rejected' => 'Refuse', 'disputed' => 'Litige', 'open' => 'Ouvert', 'under_review' => 'En revue', 'returned' => 'Restitue', 'retained' => 'Retenue', default => ucfirst(str_replace('_', ' ', (string) $status)), }; }; $transactionSource = function ($transaction): string { if ($transaction->food_order_id) return 'Food'; if ($transaction->equipment_rental_id) return 'Location'; if ($transaction->booking_id) return 'Service'; return 'Paiement'; }; $transactionReference = function ($transaction): string { if ($transaction->booking?->booking_number) return $transaction->booking->booking_number; if ($transaction->equipmentRentalRequest?->request_number) return $transaction->equipmentRentalRequest->request_number; if ($transaction->foodOrder?->order_number) return $transaction->foodOrder->order_number; return 'TX-' . $transaction->id; }; $transactionClient = function ($transaction): string { return $transaction->foodOrder?->client?->name ?? $transaction->equipmentRentalRequest?->client?->user?->name ?? $transaction->booking?->client?->user?->name ?? $transaction->user?->name ?? 'N/A'; }; $transactionPrestataire = function ($transaction): string { return $transaction->foodOrder?->prestataire?->user?->name ?? $transaction->equipmentRentalRequest?->prestataire?->user?->name ?? $transaction->booking?->prestataire?->user?->name ?? 'N/A'; }; $escrowValue = function ($row, array $keys, $default = null) { foreach ($keys as $key) { if (isset($row->{$key}) && $row->{$key} !== null && $row->{$key} !== '') { return $row->{$key}; } } return $default; }; $escrowTypeLabel = function (?string $type): string { $type = (string) $type; return match (true) { str_contains($type, 'Booking') => 'Service', str_contains($type, 'EquipmentRental') => 'Location', str_contains($type, 'UrgentSale') => 'Vente urgente', default => class_basename($type ?: 'Escrow'), }; }; $fieldClass = 'rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm text-slate-900'; $textAreaClass = 'rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900'; $primaryButtonClass = 'inline-flex items-center justify-center rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white'; $secondaryButtonClass = 'inline-flex items-center justify-center rounded-xl bg-slate-100 px-4 py-2.5 text-sm font-semibold text-slate-700'; $successButtonClass = 'inline-flex items-center justify-center rounded-xl bg-green-700 px-4 py-2.5 text-sm font-semibold text-white'; $warningButtonClass = 'inline-flex items-center justify-center rounded-xl bg-amber-600 px-4 py-2.5 text-sm font-semibold text-white'; $dangerButtonClass = 'inline-flex items-center justify-center rounded-xl bg-red-700 px-4 py-2.5 text-sm font-semibold text-white'; $infoButtonClass = 'inline-flex items-center justify-center rounded-xl bg-indigo-700 px-4 py-2.5 text-sm font-semibold text-white'; $kpiCards = [ [ 'label' => 'Volume brut', 'value' => $money($stats['gross_volume'] ?? 0), 'value_class' => 'text-slate-900', 'hint' => 'Tous flux confondus', 'icon' => 'fa-wallet', 'icon_wrap_class' => 'bg-slate-100', 'icon_class' => 'text-slate-700 text-xl', ], [ 'label' => 'Paiements valides', 'value' => number_format((int) ($stats['validated_count'] ?? 0)), 'value_class' => 'text-green-700', 'hint' => $money($stats['deposit_amount'] ?? 0) . " d'acomptes traces", 'icon' => 'fa-check-circle', 'icon_wrap_class' => 'bg-green-100', 'icon_class' => 'text-green-700 text-xl', ], [ 'label' => 'En attente / bloques', 'value' => number_format((int) ($stats['pending_count'] ?? 0)), 'value_class' => 'text-amber-700', 'hint' => $money($stats['escrow_held_amount'] ?? 0) . ' sous escrow', 'icon' => 'fa-hourglass-half', 'icon_wrap_class' => 'bg-amber-100', 'icon_class' => 'text-amber-700 text-xl', ], [ 'label' => 'Demandes remboursement', 'value' => number_format((int) ($stats['refund_pending_count'] ?? 0)), 'value_class' => 'text-rose-700', 'hint' => $money($stats['refund_pending_amount'] ?? 0) . ' a traiter', 'icon' => 'fa-rotate-left', 'icon_wrap_class' => 'bg-rose-100', 'icon_class' => 'text-rose-700 text-xl', ], [ 'label' => 'Escrows en cours', 'value' => number_format((int) ($stats['escrow_held_count'] ?? 0)), 'value_class' => 'text-indigo-700', 'hint' => $money($stats['escrow_held_amount'] ?? 0), 'icon' => 'fa-shield-halved', 'icon_wrap_class' => 'bg-indigo-100', 'icon_class' => 'text-indigo-700 text-xl', ], [ 'label' => 'Cautions ouvertes', 'value' => number_format((int) ($stats['caution_open_count'] ?? 0)), 'value_class' => 'text-cyan-700', 'hint' => $money($stats['caution_open_amount'] ?? 0), 'icon' => 'fa-box-open', 'icon_wrap_class' => 'bg-cyan-100', 'icon_class' => 'text-cyan-700 text-xl', ], [ 'label' => 'Food a capturer', 'value' => number_format((int) ($stats['food_pending_capture_count'] ?? 0)), 'value_class' => 'text-fuchsia-700', 'hint' => $money($stats['food_pending_capture_amount'] ?? 0), 'icon' => 'fa-bag-shopping', 'icon_wrap_class' => 'bg-fuchsia-100', 'icon_class' => 'text-fuchsia-700 text-xl', ], [ 'label' => 'Paiements en echec', 'value' => number_format((int) ($stats['failed_count'] ?? 0)), 'value_class' => 'text-red-700', 'hint' => $money($stats['refunded_amount'] ?? 0) . ' deja remboursee', 'icon' => 'fa-triangle-exclamation', 'icon_wrap_class' => 'bg-red-100', 'icon_class' => 'text-red-700 text-xl', ], ]; $quickLinks = [ ['href' => '#actions-urgentes', 'label' => 'Actions urgentes', 'class' => 'bg-slate-900 text-white'], ['href' => '#transactions', 'label' => 'Transactions', 'class' => 'bg-slate-100 text-slate-700'], ['href' => '#escrows', 'label' => 'Escrows', 'class' => 'bg-slate-100 text-slate-700'], ['href' => '#remboursements', 'label' => 'Remboursements', 'class' => 'bg-slate-100 text-slate-700'], ]; @endphp @section('content') @if(session('success'))
{{ session('success') }}
@endif @if(session('error'))
{{ session('error') }}
@endif @if(!empty($pageError ?? null))
{{ $pageError }}
@endif @if(!empty($pageWarnings ?? []))
Certaines sections n'ont pas pu etre chargees correctement : {{ implode(', ', array_unique($pageWarnings)) }}.
@endif

Console de controle

Vue unique des flux et des actions admin

Consulte les flux modernes et legacy, puis traite les dossiers urgents sans parcourir plusieurs pages.

@foreach($quickLinks as $quickLink) {{ $quickLink['label'] }} @endforeach
@foreach($kpiCards as $kpiCard)

{{ $kpiCard['label'] }}

{{ $kpiCard['value'] }}

{{ $kpiCard['hint'] }}

@endforeach

Filtres

Affiner les resultats

Recherche par reference, statut, source, mode de paiement ou periode.

Analytics
Reinitialiser

Priorites

Actions urgentes

Les dossiers ci-dessous concentrent les actions manuelles importantes.

Remboursements

Demandes a valider

Approuve ou rejette les demandes avec une note admin.

{{ $refundRequests->total() ?? 0 }} demandes
@forelse(collect($refundRequests->items())->take(6) as $refund)
Refund #{{ $refund->id }} {{ $statusLabel($refund->status) }}

{{ $money($refund->amount) }} sur {{ $refund->transaction?->id ? 'TX-' . $refund->transaction->id : 'transaction non reliee' }}

Client: {{ $refund->user?->name ?? 'N/A' }}
Reference: {{ $refund->transaction?->transaction_id ?? $refund->transaction?->stripe_payment_intent_id ?? 'N/A' }}
Ouvrir le dossier

Motif

{{ $refund->reason ?: 'Aucun motif fourni.' }}

Email client: {{ $refund->user?->email ?? 'N/A' }}

@if($refund->status === 'pending')
@csrf
@endif
@empty
Aucune demande de remboursement a traiter.
@endforelse

Food

Autorisations et remboursements

Capture, annulation d'autorisation ou remboursement selon l'etat du dossier.

{{ $foodActionQueue->count() }} dossiers
@forelse($foodActionQueue as $foodOrder)
{{ $foodOrder->order_number ?? ('FO-' . $foodOrder->id) }} {{ $statusLabel($foodOrder->payment_status) }}
Client: {{ $foodOrder->client?->name ?? 'N/A' }}
Prestataire: {{ $foodOrder->prestataire?->user?->name ?? 'N/A' }}
Escrow: {{ $statusLabel($foodOrder->escrow_status) }}
Methode: {{ $foodOrder->payment_method ?? 'N/A' }}

{{ $money($foodOrder->amount_held ?? $foodOrder->total) }}

Ouvrir les actions

@csrf

Capturer le paiement

Encaisse le montant autorise pour cette commande.

@csrf

Annuler l'autorisation

@csrf

Rembourser le client

@empty
Aucun flux food necessitant une action immediate.
@endforelse

Litiges escrow

Dossiers ouverts

Analyse le litige et tranche avec une resolution tracee.

{{ $disputeQueue->count() }} ouverts
@forelse($disputeQueue as $dispute)
Litige #{{ $dispute->id }} {{ $statusLabel($dispute->status) }}

{{ $escrowTypeLabel($dispute->escrowable_type) }}

Client: {{ $dispute->client_name ?? 'N/A' }}
Prestataire: {{ $dispute->prestataire_name ?? 'N/A' }}
Ouvrir le dossier

Description

{{ $dispute->description }}

@csrf
@empty
Aucun litige escrow ouvert.
@endforelse

Cautions materiel

Retours a traiter

Traite l'etat du materiel et la retenue eventuelle sur caution.

{{ $equipmentDepositQueue->count() }} dossiers
@forelse($equipmentDepositQueue as $entry) @php($rentalRequest = $entry['request']) @php($escrow = $entry['escrow'])
{{ $rentalRequest->request_number ?? ('LOC-' . $rentalRequest->id) }} {{ $statusLabel($rentalRequest->deposit_status ?? $escrow->status ?? 'pending') }}
Client: {{ $rentalRequest->client?->user?->name ?? 'N/A' }}
Prestataire: {{ $rentalRequest->prestataire?->user?->name ?? 'N/A' }}
Materiel: {{ $rentalRequest->equipment?->name ?? 'N/A' }}
Escrow: {{ $escrow ? ('#' . $escrow->id . ' · ' . $statusLabel($escrow->status)) : 'Aucun escrow detecte' }}

{{ $money($rentalRequest->security_deposit) }}

Ouvrir les actions

@if($escrow)
@csrf
@else
Aucun escrow de caution detecte pour ce dossier.
@endif
@empty
Aucune caution ouverte a traiter.
@endforelse

Flux modernes

Transactions

Historique detaille des paiements modernes avec action de remboursement.

{{ $transactions->total() ?? 0 }} lignes
@if(($tableAvailability['payment_transactions'] ?? false) && $transactions->count() > 0)
@foreach($transactions as $transaction) @endforeach
Reference Flux Parties Montant Statut Date Details et actions
{{ $transactionReference($transaction) }}
TX-{{ $transaction->id }}
{{ $transactionSource($transaction) }}
{{ $transactionClient($transaction) }}
Prestataire: {{ $transactionPrestataire($transaction) }}
{{ $money($transaction->amount) }} {{ $statusLabel($transaction->status) }} {{ optional($transaction->created_at)->format('d/m/Y H:i') }}
Voir / rembourser
Type: {{ $transaction->type ?? 'payment' }}
Methode: {{ $transaction->payment_method ?? 'N/A' }} / {{ $transaction->provider ?? 'N/A' }}
Stripe: {{ $transaction->stripe_payment_intent_id ?? 'N/A' }}
Transaction externe: {{ $transaction->transaction_id ?? 'N/A' }}
@if(in_array((string) $transaction->status, ['paid', 'completed', 'held', 'released', 'partially_refunded'], true))
@csrf
@endif
{{ $transactions->appends(request()->except('transactions_page'))->links() }}
@else
Aucune transaction visible avec les filtres actuels.
@endif

Source historique

Transactions legacy

Anciennes lignes lues depuis la table legacy `transactions`.

{{ $legacyTransactions->total() ?? 0 }} lignes
@if(($legacyTransactions->total() ?? 0) > 0)
@foreach($legacyTransactions as $legacyTransaction) @endforeach
Reference Client Prestataire Montant Type Statut Date
{{ $legacyTransaction->reference ?? ('LG-' . $legacyTransaction->id) }} {{ $legacyTransaction->user_name ?? $legacyTransaction->user_email ?? 'N/A' }} {{ $legacyTransaction->prestataire_name ?? 'N/A' }} {{ $money($legacyTransaction->amount) }} {{ $legacyTransaction->type ?? 'payment' }} {{ $statusLabel($legacyTransaction->status) }} {{ \Carbon\Carbon::parse($legacyTransaction->created_at)->format('d/m/Y H:i') }}
{{ $legacyTransactions->appends(request()->except('legacy_transactions_page'))->links() }}
@else
Aucune transaction legacy visible avec les filtres actuels.
@endif

Escrows

Escrows et etapes

Suivi des montants bloques, des etapes et des actions de liberation ou remboursement.

{{ $escrows->total() ?? 0 }} lignes
@if(($tableAvailability['escrow_transactions'] ?? false) && $escrows->count() > 0)
@foreach($escrows as $escrow) @php($escrowAmount = $escrowValue($escrow, ['total_amount', 'amount'], 0)) @php($escrowDeposit = $escrowValue($escrow, ['deposit_amount'], 0)) @php($escrowRemaining = $escrowValue($escrow, ['remaining_amount'], $escrowAmount))
ESC-{{ $escrow->id }}
{{ $escrowTypeLabel($escrow->escrowable_type) }}
{{ $escrow->client_name ?? 'N/A' }}
{{ $escrow->prestataire_name ?? 'N/A' }}
{{ $money($escrowAmount) }}
{{ $statusLabel($escrow->status) }}
Caution: {{ $money($escrowDeposit) }}
Reste a debloquer: {{ $money($escrowRemaining) }}
PI Stripe: {{ $escrow->stripe_payment_intent_id ?? 'N/A' }}
Transfer Stripe: {{ $escrow->stripe_transfer_id ?? 'N/A' }}
@if(!empty($escrow->dispute_id))
Litige associe: #{{ $escrow->dispute_id }} · {{ $statusLabel($escrow->dispute_status) }}
@endif
@csrf
@csrf
@endforeach
{{ $escrows->appends(request()->except('escrows_page'))->links() }}
@else
Aucun escrow visible avec les filtres actuels.
@endif

Historique

Journal remboursements

Journal de suivi des demandes et statuts de remboursement.

{{ $refundRequests->total() ?? 0 }} lignes
@if(($tableAvailability['refunds'] ?? false) && $refundRequests->count() > 0)
@foreach($refundRequests as $refund) @endforeach
ID Client Transaction Montant Statut Motif
RF-{{ $refund->id }} {{ $refund->user?->name ?? 'N/A' }} {{ $refund->transaction?->transaction_id ?? ('TX-' . ($refund->transaction_id ?? 'N/A')) }} {{ $money($refund->amount) }} {{ $statusLabel($refund->status) }} {{ \Illuminate\Support\Str::limit($refund->reason, 80) }}
{{ $refundRequests->appends(request()->except('refunds_page'))->links() }}
@else
Aucun remboursement visible avec les filtres actuels.
@endif
@endsection