14-04-2026
This commit is contained in:
parent
f58c709945
commit
0f82fea88a
72 changed files with 7414 additions and 148 deletions
123
app/Models/CheckoutFunnelEvent.php
Normal file
123
app/Models/CheckoutFunnelEvent.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $event checkout_visited|form_submitted|payment_initiated|payment_returned|payment_confirmed
|
||||
* @property string|null $session_id
|
||||
* @property string|null $domain
|
||||
* @property int|null $shopping_user_id
|
||||
* @property int|null $shopping_order_id
|
||||
* @property int|null $shopping_payment_id
|
||||
* @property int|null $consultant_user_id
|
||||
* @property string|null $payment_method
|
||||
* @property string|null $return_status
|
||||
* @property int|null $amount_cents
|
||||
* @property array|null $metadata
|
||||
* @property \Illuminate\Support\Carbon $created_at
|
||||
*/
|
||||
class CheckoutFunnelEvent extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'event',
|
||||
'session_id',
|
||||
'domain',
|
||||
'shopping_user_id',
|
||||
'shopping_order_id',
|
||||
'shopping_payment_id',
|
||||
'consultant_user_id',
|
||||
'payment_method',
|
||||
'return_status',
|
||||
'amount_cents',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'metadata' => 'array',
|
||||
'amount_cents' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function shopping_order(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShoppingOrder::class);
|
||||
}
|
||||
|
||||
public function shopping_payment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShoppingPayment::class);
|
||||
}
|
||||
|
||||
public function consultant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'consultant_user_id');
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public static function eventLabels(): array
|
||||
{
|
||||
return [
|
||||
'checkout_visited' => 'Checkout aufgerufen',
|
||||
'form_submitted' => 'Formular abgeschickt',
|
||||
'payment_initiated' => 'Zahlung gestartet',
|
||||
'payment_returned' => 'Zurück von PAYONE',
|
||||
'payment_confirmed' => 'PAYONE Callback',
|
||||
];
|
||||
}
|
||||
|
||||
public function getEventLabelAttribute(): string
|
||||
{
|
||||
return self::eventLabels()[$this->event] ?? $this->event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies the domain into a human-readable source channel.
|
||||
*
|
||||
* - my.mivita.* → Salescenter (Berater-Bereich)
|
||||
* - in.mivita.* → Beraterzugang (in.mivita.care login)
|
||||
* - *.mivita.care → Kundenshop (Live)
|
||||
* - *.mivita.shop → Kundenshop (Live)
|
||||
* - *.mivita.test → Testserver
|
||||
*/
|
||||
public function getSourceTypeAttribute(): string
|
||||
{
|
||||
$domain = strtolower($this->domain ?? '');
|
||||
|
||||
if (str_starts_with($domain, 'my.')) {
|
||||
return 'salescenter';
|
||||
}
|
||||
|
||||
if (str_starts_with($domain, 'in.')) {
|
||||
return 'beraterzugang';
|
||||
}
|
||||
|
||||
if (str_ends_with($domain, '.test') || str_contains($domain, '.mivita.test')) {
|
||||
return 'testserver';
|
||||
}
|
||||
|
||||
if (str_ends_with($domain, '.care') || str_ends_with($domain, '.shop')) {
|
||||
return 'kundenshop';
|
||||
}
|
||||
|
||||
return 'unbekannt';
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public static function sourceLabels(): array
|
||||
{
|
||||
return [
|
||||
'kundenshop' => 'Kundenshop (*.mivita.care / *.mivita.shop)',
|
||||
'salescenter' => 'Salescenter (my.mivita.care)',
|
||||
'beraterzugang' => 'Beraterzugang (in.mivita.care)',
|
||||
'testserver' => 'Testserver (*.mivita.test)',
|
||||
'unbekannt' => 'Unbekannt',
|
||||
];
|
||||
}
|
||||
}
|
||||
46
app/Models/IncidentActivity.php
Normal file
46
app/Models/IncidentActivity.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class IncidentActivity extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'incident_id',
|
||||
'type',
|
||||
'title',
|
||||
'content',
|
||||
'author',
|
||||
];
|
||||
|
||||
public function incident(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PaymentIncident::class, 'incident_id');
|
||||
}
|
||||
|
||||
public function getTypeIconAttribute(): string
|
||||
{
|
||||
return match ($this->type) {
|
||||
'email' => 'ion-md-mail',
|
||||
'call' => 'ion-md-call',
|
||||
'ticket' => 'ion-md-pricetag',
|
||||
'status_change' => 'ion-md-swap',
|
||||
'provider_response' => 'ion-md-return-left',
|
||||
default => 'ion-md-create',
|
||||
};
|
||||
}
|
||||
|
||||
public function getTypeLabelAttribute(): string
|
||||
{
|
||||
return match ($this->type) {
|
||||
'email' => 'E-Mail',
|
||||
'call' => 'Telefonat',
|
||||
'ticket' => 'Ticket',
|
||||
'status_change' => 'Statusänderung',
|
||||
'provider_response' => 'Anbieter-Antwort',
|
||||
default => 'Notiz',
|
||||
};
|
||||
}
|
||||
}
|
||||
156
app/Models/PaymentIncident.php
Normal file
156
app/Models/PaymentIncident.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class PaymentIncident extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'notes',
|
||||
'provider',
|
||||
'type',
|
||||
'status',
|
||||
'severity',
|
||||
'affected_orders',
|
||||
'affected_revenue',
|
||||
'detected_at',
|
||||
'resolved_at',
|
||||
'ticket_number',
|
||||
];
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
protected $attributes = [
|
||||
'status' => 'open',
|
||||
'severity' => 'medium',
|
||||
'provider' => 'payone',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'detected_at' => 'datetime',
|
||||
'resolved_at' => 'datetime',
|
||||
'affected_revenue' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
public function activities(): HasMany
|
||||
{
|
||||
return $this->hasMany(IncidentActivity::class, 'incident_id')->orderBy('created_at');
|
||||
}
|
||||
|
||||
public function scopeOpen(Builder $query): Builder
|
||||
{
|
||||
return $query->whereIn('status', ['open', 'in_progress', 'waiting_provider']);
|
||||
}
|
||||
|
||||
public function scopePayone(Builder $query): Builder
|
||||
{
|
||||
return $query->where('provider', 'payone');
|
||||
}
|
||||
|
||||
public function scopeLastDays(Builder $query, int $days): Builder
|
||||
{
|
||||
return $query->where('detected_at', '>=', now()->subDays($days));
|
||||
}
|
||||
|
||||
public function getDurationAttribute(): string
|
||||
{
|
||||
$end = $this->resolved_at ?? now();
|
||||
$diff = $this->detected_at->diff($end);
|
||||
|
||||
if ($diff->days > 0) {
|
||||
return $diff->days.'d '.$diff->h.'h';
|
||||
}
|
||||
|
||||
if ($diff->h > 0) {
|
||||
return $diff->h.'h '.$diff->i.'min';
|
||||
}
|
||||
|
||||
return $diff->i.' min';
|
||||
}
|
||||
|
||||
public function getSeverityColorAttribute(): string
|
||||
{
|
||||
return match ($this->severity) {
|
||||
'critical' => 'danger',
|
||||
'high' => 'warning',
|
||||
'medium' => 'info',
|
||||
'low' => 'success',
|
||||
default => 'secondary',
|
||||
};
|
||||
}
|
||||
|
||||
public function getSeverityLabelAttribute(): string
|
||||
{
|
||||
return match ($this->severity) {
|
||||
'critical' => 'Kritisch',
|
||||
'high' => 'Hoch',
|
||||
'medium' => 'Mittel',
|
||||
'low' => 'Niedrig',
|
||||
default => $this->severity,
|
||||
};
|
||||
}
|
||||
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
'open' => 'Offen',
|
||||
'in_progress' => 'In Bearbeitung',
|
||||
'waiting_provider' => 'Wartet auf Anbieter',
|
||||
'resolved' => 'Gelöst',
|
||||
'closed' => 'Geschlossen',
|
||||
default => $this->status,
|
||||
};
|
||||
}
|
||||
|
||||
public function getStatusColorAttribute(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
'open' => 'danger',
|
||||
'in_progress' => 'warning',
|
||||
'waiting_provider' => 'info',
|
||||
'resolved' => 'success',
|
||||
'closed' => 'secondary',
|
||||
default => 'secondary',
|
||||
};
|
||||
}
|
||||
|
||||
public function getTypeIconAttribute(): string
|
||||
{
|
||||
return match ($this->type) {
|
||||
'outage' => 'ion-md-power',
|
||||
'ipn_error' => 'ion-md-git-network',
|
||||
'payment_failure' => 'ion-md-card',
|
||||
'slow_response' => 'ion-md-timer',
|
||||
default => 'ion-md-alert',
|
||||
};
|
||||
}
|
||||
|
||||
public function getTypeLabelAttribute(): string
|
||||
{
|
||||
return match ($this->type) {
|
||||
'outage' => 'Ausfall',
|
||||
'ipn_error' => 'IPN-Fehler',
|
||||
'payment_failure' => 'Zahlungsfehler',
|
||||
'slow_response' => 'Langsame Antwort',
|
||||
default => 'Sonstiges',
|
||||
};
|
||||
}
|
||||
|
||||
public function getProviderLabelAttribute(): string
|
||||
{
|
||||
return match ($this->provider) {
|
||||
'payone' => 'PAYONE',
|
||||
// 'stripe' => 'Stripe', // aktuell nicht aktiv
|
||||
'paypal' => 'PayPal',
|
||||
// 'mollie' => 'Mollie', // aktuell nicht aktiv
|
||||
default => 'Sonstige',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\ShoppingPayment $shopping_payment
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction query()
|
||||
|
|
@ -39,18 +40,20 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction whereTxid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction whereUserid($value)
|
||||
*
|
||||
* @property string|null $mode
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\PaymentTransaction whereMode($value)
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class PaymentTransaction extends Model
|
||||
{
|
||||
protected $table = 'payment_transactions';
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'transmitted_data' => 'array'
|
||||
];
|
||||
'transmitted_data' => 'array',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'shopping_payment_id',
|
||||
|
|
@ -67,9 +70,52 @@ class PaymentTransaction extends Model
|
|||
'mode',
|
||||
];
|
||||
|
||||
|
||||
public function shopping_payment()
|
||||
{
|
||||
return $this->belongsTo('App\Models\ShoppingPayment','shopping_payment_id');
|
||||
return $this->belongsTo('App\Models\ShoppingPayment', 'shopping_payment_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description for known PAYONE error codes.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function payoneErrorDescriptions(): array
|
||||
{
|
||||
return [
|
||||
2 => 'Allgemeiner Fehler',
|
||||
4 => 'Karte gesperrt',
|
||||
5 => 'Zahlung abgelehnt',
|
||||
12 => 'Ungültige Kartennummer',
|
||||
14 => 'Ungültige Kartendaten',
|
||||
33 => 'Karte abgelaufen',
|
||||
34 => 'Karte gesperrt/gestohlen',
|
||||
55 => 'Falsche PIN',
|
||||
56 => 'Kunde hat abgebrochen',
|
||||
58 => 'Transaktion nicht erlaubt',
|
||||
62 => 'Karte für internationale Transaktionen nicht zugelassen',
|
||||
104 => 'Karte für Online-Zahlungen nicht erlaubt',
|
||||
105 => 'Karte ungültig',
|
||||
120 => 'Falsche Prüfziffer (CVV)',
|
||||
130 => 'Limit überschritten',
|
||||
131 => 'Währung nicht unterstützt',
|
||||
135 => 'Bank antwortet nicht',
|
||||
136 => 'Bank nicht gefunden',
|
||||
137 => 'Betrag stimmt nicht überein',
|
||||
900 => '3D-Secure Authentifizierung fehlgeschlagen',
|
||||
902 => 'Bank hat abgelehnt',
|
||||
970 => 'Keine Antwort (Timeout)',
|
||||
4218 => 'Karte unter Betrugsverdacht',
|
||||
4219 => 'Transaktion von Kartenaussteller abgelehnt',
|
||||
];
|
||||
}
|
||||
|
||||
public function getErrorDescriptionAttribute(): ?string
|
||||
{
|
||||
if (! $this->errorcode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::payoneErrorDescriptions()[(int) $this->errorcode] ?? null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
app/Models/ProviderUptimeLog.php
Normal file
24
app/Models/ProviderUptimeLog.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProviderUptimeLog extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'provider',
|
||||
'is_up',
|
||||
'response_time_ms',
|
||||
'error_message',
|
||||
'checked_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_up' => 'boolean',
|
||||
'checked_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue