mivita/resources/views/admin/payment-dashboard/abandoned.blade.php
2026-04-14 18:07:45 +02:00

457 lines
29 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.layout-2')
@section('content')
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
<i class="ion ion-md-arrow-back"></i> Zurück
</a>
<strong>Abbruch-Analyse</strong>
<small class="text-muted ml-2">Nicht gestartete, abgebrochene und technisch fehlerhafte Zahlungen</small>
</div>
{{-- Zeitraum-Filter --}}
<form method="GET" class="form-inline">
<label class="mr-2 text-muted small">Zeitraum:</label>
<select name="days" class="form-control form-control-sm mr-2" onchange="this.form.submit()">
@foreach ([7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 60 => '60 Tage', 90 => '90 Tage', 0 => 'Alle'] as $value => $label)
<option value="{{ $value }}" {{ $days == $value ? 'selected' : '' }}>{{ $label }}</option>
@endforeach
</select>
</form>
</div>
{{-- Stat-Karten --}}
<div class="row mb-4">
<div class="col-md-4">
<div class="card border-warning">
<div class="card-body text-center py-3">
<div class="text-muted small mb-1">Zahlung nie gestartet</div>
<div class="h3 font-weight-bold mb-0 text-warning">{{ $abandonedStats['no_payment'] }}</div>
<small class="text-muted">Orders mit txaction=prev ohne Payment</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-danger">
<div class="card-body text-center py-3">
<div class="text-muted small mb-1">Abgebrochen / Fehler</div>
<div class="h3 font-weight-bold mb-0 text-danger">{{ $abandonedStats['cancelled'] }}</div>
<small class="text-muted">cancel + error Payments</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-secondary">
<div class="card-body text-center py-3">
<div class="text-muted small mb-1">Kein PAYONE-Callback</div>
<div class="h3 font-weight-bold mb-0 text-secondary">{{ $abandonedStats['no_callback'] }}</div>
<small class="text-muted">Payments ohne Transaktion (>2h)</small>
</div>
</div>
</div>
</div>
{{-- Tabs --}}
<ul class="nav nav-tabs mb-0" id="abandonedTabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="tab-no-payment" data-toggle="tab" href="#no-payment" role="tab">
Nie gestartet
@if ($abandonedStats['no_payment'] > 0)
<span class="badge badge-warning ml-1">{{ $abandonedStats['no_payment'] }}</span>
@endif
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab-cancelled" data-toggle="tab" href="#cancelled" role="tab">
Abgebrochen / Fehler
@if ($abandonedStats['cancelled'] > 0)
<span class="badge badge-danger ml-1">{{ $abandonedStats['cancelled'] }}</span>
@endif
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab-no-callback" data-toggle="tab" href="#no-callback" role="tab">
Kein Callback
@if ($abandonedStats['no_callback'] > 0)
<span class="badge badge-secondary ml-1">{{ $abandonedStats['no_callback'] }}</span>
@endif
</a>
</li>
</ul>
<div class="tab-content border border-top-0 rounded-bottom bg-white px-3 pt-3 pb-2 mb-4">
{{-- Tab 1: Orders ohne Payment --}}
<div class="tab-pane fade show active" id="no-payment" role="tabpanel">
<p class="text-muted small mt-1 mb-3">
Bestellungen, bei denen der Benutzer den Checkout-Prozess zwar abgeschlossen hat (txaction=prev),
aber die Zahlung nie initiiert wurde. Mindestens 30 Minuten alt.
</p>
@if ($ordersWithoutPayment->isEmpty())
<div class="alert alert-success">Keine offenen Bestellungen ohne Zahlung im gewählten Zeitraum.</div>
@else
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr>
<th>Order-ID</th>
<th>Kunde / Berater</th>
<th>Typ</th>
<th>Betrag</th>
<th>Erstellt</th>
<th>Vor</th>
</tr>
</thead>
<tbody>
@foreach ($ordersWithoutPayment as $order)
@php
$isConsultant = $order->auth_user_id && $order->auth_user;
if ($isConsultant) {
$name = trim(
($order->auth_user->firstname ?? '') .
' ' .
($order->auth_user->lastname ?? ''),
);
$email = $order->auth_user->email ?? '';
} else {
$name = trim(
($order->shopping_user->billing_firstname ?? '') .
' ' .
($order->shopping_user->billing_lastname ?? ''),
);
$email = $order->shopping_user->billing_email ?? '';
}
@endphp
<tr>
<td>
<a href="{{ $order->auth_user_id ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
target="_blank" class="text-monospace">
#{{ $order->id }}
</a>
</td>
<td>
@if ($isConsultant)
<span class="badge badge-primary badge-sm mr-1">Berater</span>
@else
<span class="badge badge-info badge-sm mr-1">Kunde</span>
@endif
<strong>{{ $name ?: '' }}</strong>
<br><small class="text-muted">{{ $email }}</small>
</td>
<td><small>{{ $order->payment_for ?? '' }}</small></td>
<td class="font-weight-bold">
{{ $order->price_total ? number_format($order->price_total, 2, ',', '.') . ' €' : '' }}
</td>
<td class="text-muted small">
{{ $order->created_at ? $order->created_at->format('d.m.Y H:i') : '' }}</td>
<td class="text-muted small">
{{ $order->created_at ? $order->created_at->diffForHumans() : '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $ordersWithoutPayment->links() }}
@endif
</div>
{{-- Tab 2: Abgebrochene / Fehler --}}
<div class="tab-pane fade" id="cancelled" role="tabpanel">
<p class="text-muted small mt-1 mb-3">
Zahlungen, bei denen der Nutzer aktiv abgebrochen hat (<code>cancel</code>) oder bei denen PAYONE
einen Fehler zurückgemeldet hat (<code>error</code>). Zeile anklicken für PAYONE-Fehlerdetails.
</p>
@if ($cancelledPayments->isEmpty())
<div class="alert alert-success">Keine abgebrochenen Zahlungen im gewählten Zeitraum.</div>
@else
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="thead-light">
<tr>
<th style="width:1rem"></th>
<th>Referenz</th>
<th>Order-ID</th>
<th>Kunde / Berater</th>
<th>Betrag</th>
<th>Status</th>
<th>Zahlungsart</th>
<th>Zeitpunkt</th>
<th>Vor</th>
</tr>
</thead>
<tbody>
@foreach ($cancelledPayments as $payment)
@php
$order = $payment->shopping_order;
$isConsultant = $order && $order->auth_user_id && $order->auth_user;
if ($order && $isConsultant) {
$name = trim(
($order->auth_user->firstname ?? '') .
' ' .
($order->auth_user->lastname ?? ''),
);
$email = $order->auth_user->email ?? '';
} elseif ($order && $order->shopping_user) {
$name = trim(
($order->shopping_user->billing_firstname ?? '') .
' ' .
($order->shopping_user->billing_lastname ?? ''),
);
$email = $order->shopping_user->billing_email ?? '';
} else {
$name = '';
$email = '';
}
$hasTransactions = $payment->payment_transactions->isNotEmpty();
$collapseId = 'cancelled-tx-' . $payment->id;
@endphp
{{-- Hauptzeile --}}
<tr class="{{ $hasTransactions ? 'cursor-pointer' : '' }}"
@if($hasTransactions) data-toggle="collapse" data-target="#{{ $collapseId }}" @endif>
<td class="text-center text-muted">
@if($hasTransactions)
<i class="ion ion-md-chevron-forward toggle-icon" style="font-size:0.85rem"></i>
@endif
</td>
<td class="text-monospace small">{{ $payment->reference }}</td>
<td>
@if ($order)
<a href="{{ $isConsultant ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
target="_blank" onclick="event.stopPropagation()">#{{ $order->id }}</a>
@else
<span class="text-muted"></span>
@endif
</td>
<td>
@if ($order)
@if ($isConsultant)
<span class="badge badge-primary badge-sm mr-1">Berater</span>
@else
<span class="badge badge-info badge-sm mr-1">Kunde</span>
@endif
<strong>{{ $name ?: '' }}</strong>
<br><small class="text-muted">{{ $email }}</small>
@else
<span class="text-muted"></span>
@endif
</td>
<td class="font-weight-bold">
{{ $payment->amount ? number_format($payment->amount / 100, 2, ',', '.') . ' €' : '' }}
</td>
<td>
@if ($payment->status === 'cancel')
<span class="badge badge-warning">Abgebrochen</span>
@elseif($payment->status === 'error')
<span class="badge badge-danger">Fehler</span>
@else
<span class="badge badge-secondary">{{ $payment->status }}</span>
@endif
</td>
<td class="small">{{ $payment->payment_type ?? '' }}</td>
<td class="text-muted small">
{{ $payment->created_at ? $payment->created_at->format('d.m.Y H:i') : '' }}</td>
<td class="text-muted small">
{{ $payment->created_at ? $payment->created_at->diffForHumans() : '' }}</td>
</tr>
{{-- Aufklappbare Fehlerdetails --}}
@if($hasTransactions)
<tr class="collapse" id="{{ $collapseId }}">
<td colspan="9" class="p-0">
<div class="bg-light border-top border-bottom px-3 py-2">
<small class="text-muted font-weight-bold d-block mb-2">
PAYONE-Transaktionen ({{ $payment->payment_transactions->count() }})
</small>
@foreach($payment->payment_transactions as $tx)
<div class="card card-body p-2 mb-2 {{ $tx->errorcode ? 'border-danger' : 'border-secondary' }}" style="font-size:0.78rem">
<div class="row">
<div class="col-md-3">
<span class="text-muted">TX-ID:</span>
<strong>{{ $tx->txid ?? '' }}</strong><br>
<span class="text-muted">Action:</span>
<code>{{ $tx->txaction ?? '' }}</code><br>
<span class="text-muted">Request:</span>
<code>{{ $tx->request ?? '' }}</code><br>
<span class="text-muted">Status:</span>
@if($tx->status === 'approved')
<span class="badge badge-success">approved</span>
@elseif($tx->status === 'error')
<span class="badge badge-danger">error</span>
@else
<span class="badge badge-secondary">{{ $tx->status ?? '' }}</span>
@endif
</div>
<div class="col-md-4">
@php
$errorcode = $tx->errorcode
?? ($tx->transmitted_data['errorcode'] ?? null);
$failedcause = $tx->transmitted_data['failedcause'] ?? null;
$errormessage = $tx->errormessage
?? ($tx->transmitted_data['errormessage'] ?? null);
$customermessage = $tx->customermessage
?? ($tx->transmitted_data['customermessage'] ?? null);
$description = $tx->error_description;
@endphp
@if($errorcode)
<span class="text-danger font-weight-bold">
<i class="ion ion-md-warning"></i>
Fehlercode {{ $errorcode }}
</span><br>
@if($description)
<span class="font-weight-bold text-dark">{{ $description }}</span><br>
@endif
@if($errormessage)
<span class="text-muted">PAYONE-Meldung:</span>
<span class="text-danger">{{ $errormessage }}</span><br>
@endif
@if($failedcause && $failedcause != '-'.$errorcode)
<span class="text-muted">Ursache:</span>
<code>{{ $failedcause }}</code><br>
@endif
@if($customermessage)
<span class="text-muted">Kundennachricht:</span>
<em>{{ $customermessage }}</em>
@endif
@else
<span class="text-muted font-italic">
@if($tx->txaction === 'failed')
Fehlercode nicht übermittelt
<br><small>(txaction=failed ohne Fehlercode)</small>
@elseif($tx->status === 'REDIRECT')
Nutzer zu PAYONE weitergeleitet
<br><small>(kein Fehler, Redirect)</small>
@else
Kein Fehlercode in diesem Callback
@endif
</span>
@endif
</div>
<div class="col-md-3">
<span class="text-muted">Modus:</span>
@if($tx->mode === 'test')
<span class="badge badge-warning">TEST</span>
@elseif($tx->mode === 'live')
<span class="badge badge-success">LIVE</span>
@else
<span class="text-muted"></span>
@endif
<br>
<span class="text-muted">Zeitpunkt:</span>
{{ $tx->created_at ? $tx->created_at->format('d.m.Y H:i:s') : '' }}
</div>
</div>
</div>
@endforeach
</div>
</td>
</tr>
@endif
@endforeach
</tbody>
</table>
</div>
{{ $cancelledPayments->links() }}
@endif
</div>
{{-- Tab 3: Kein Callback --}}
<div class="tab-pane fade" id="no-callback" role="tabpanel">
<p class="text-muted small mt-1 mb-3">
Zahlungen, die gestartet wurden (PAYONE-Redirect), aber nach mehr als 2 Stunden
weder einen Callback noch eine Nutzer-Rückkehr registriert haben.
Dies kann auf technische Probleme (Timeout, fehlgeschlagene Weiterleitung) hinweisen.
</p>
@if ($pendingPayments->isEmpty())
<div class="alert alert-success">Keine offenen Zahlungen ohne Callback im gewählten Zeitraum.</div>
@else
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr>
<th>Referenz</th>
<th>Order-ID</th>
<th>Kunde / Berater</th>
<th>Betrag</th>
<th>Zahlungsart</th>
<th>Modus</th>
<th>Gestartet</th>
<th>Vor</th>
</tr>
</thead>
<tbody>
@foreach ($pendingPayments as $payment)
@php
$order = $payment->shopping_order;
$isConsultant = $order && $order->auth_user_id && $order->auth_user;
if ($order && $isConsultant) {
$name = trim(
($order->auth_user->firstname ?? '') .
' ' .
($order->auth_user->lastname ?? ''),
);
$email = $order->auth_user->email ?? '';
} elseif ($order && $order->shopping_user) {
$name = trim(
($order->shopping_user->billing_firstname ?? '') .
' ' .
($order->shopping_user->billing_lastname ?? ''),
);
$email = $order->shopping_user->billing_email ?? '';
} else {
$name = '';
$email = '';
}
@endphp
<tr>
<td class="text-monospace small">{{ $payment->reference }}</td>
<td>
@if ($order)
<a href="{{ $isConsultant ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
target="_blank">#{{ $order->id }}</a>
@else
<span class="text-muted"></span>
@endif
</td>
<td>
@if ($order)
@if ($isConsultant)
<span class="badge badge-primary badge-sm mr-1">Berater</span>
@else
<span class="badge badge-info badge-sm mr-1">Kunde</span>
@endif
<strong>{{ $name ?: '' }}</strong>
<br><small class="text-muted">{{ $email }}</small>
@else
<span class="text-muted"></span>
@endif
</td>
<td class="font-weight-bold">
{{ $payment->amount ? number_format($payment->amount / 100, 2, ',', '.') . ' €' : '' }}
</td>
<td class="small">{{ $payment->payment_type ?? '' }}</td>
<td>
@if (($payment->mode ?? '') === 'test')
<span class="badge badge-warning">TEST</span>
@elseif(($payment->mode ?? '') === 'live')
<span class="badge badge-success">LIVE</span>
@else
<span class="text-muted"></span>
@endif
</td>
<td class="text-muted small">
{{ $payment->created_at ? $payment->created_at->format('d.m.Y H:i') : '' }}</td>
<td class="text-muted small">
{{ $payment->created_at ? $payment->created_at->diffForHumans() : '' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $pendingPayments->links() }}
@endif
</div>
</div>
@endsection