- Provider-Status/Uptime: Stripe-Kachel aktiviert; payment:check-uptime prüft zusätzlich api.stripe.com/v1/ (401 ohne Key = erreichbar) inkl. Auto-Incident - Zahlungen: Anbieter-Filter, Provider-Badges, Umsatz je Provider, Stripe-Dashboard-Deep-Links (ShoppingPayment/PaymentTransaction:: stripeDashboardUrl, pi_-Guard, testmodus-bewusst) - Rohe Transaktionen: Anbieter-Filter/-Spalte, Erfolgsraten je Anbieter; Bugfix: Stringable-Vergleich ließ den txaction-Filter immer mit leerem Wert greifen — Liste war dauerhaft leer - Neue Kachel „Abos je Anbieter" (Index + GF-Ansicht) mit Fehlerzählern und Stripe-Anteil-Fortschrittsbalken (Auslaufmodell PAYONE → Stripe ablesbar) - Incident-Kachel weist Stripe/PAYONE getrennt aus; Log-Viewer mit Kanal-Umschalter stripe/payone - Plan-Doku: S7.4-Umsetzungsstatus, Abo-Go-Live 2026-07-10 vermerkt Tests: PaymentDashboardProviderTest (9 neu), Dashboard-Suite 49 grün. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
220 lines
9.5 KiB
PHP
220 lines
9.5 KiB
PHP
<?php
|
||
|
||
use App\Http\Controllers\Admin\PaymentDashboardController;
|
||
use App\Models\PaymentTransaction;
|
||
use App\Models\ProviderUptimeLog;
|
||
use App\Models\ShoppingPayment;
|
||
use App\Models\UserAbo;
|
||
use App\User;
|
||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
use Illuminate\Support\Facades\Hash;
|
||
use Illuminate\Support\Facades\Http;
|
||
|
||
uses(RefreshDatabase::class);
|
||
|
||
/**
|
||
* Plan S7.4 – Payment-Dashboard für Stripe: Provider-Filter, Stats je Provider,
|
||
* Abo-Auslauf-Übersicht, Stripe-Deep-Links, Uptime-Check.
|
||
*/
|
||
function dashboardAdmin(): User
|
||
{
|
||
return User::forceCreate([
|
||
'email' => 'dash-admin-'.uniqid('', true).'@test.com',
|
||
'password' => Hash::make('secret'),
|
||
'admin' => 2,
|
||
'lang' => 'de',
|
||
]);
|
||
}
|
||
|
||
function dashboardPayment(array $overrides = []): ShoppingPayment
|
||
{
|
||
return ShoppingPayment::forceCreate(array_merge([
|
||
'shopping_order_id' => 1,
|
||
'clearingtype' => 'cc',
|
||
'reference' => 'dash-'.uniqid('', true),
|
||
'amount' => 5000,
|
||
'currency' => 'EUR',
|
||
'mode' => 'test',
|
||
], $overrides));
|
||
}
|
||
|
||
function dashboardAbo(array $overrides = []): UserAbo
|
||
{
|
||
$consultant = User::forceCreate([
|
||
'email' => 'dash-abo-'.uniqid('', true).'@test.com',
|
||
'password' => Hash::make('secret'),
|
||
'lang' => 'de',
|
||
]);
|
||
|
||
return UserAbo::create(array_merge([
|
||
'user_id' => $consultant->id,
|
||
'shopping_user_id' => 1,
|
||
'is_for' => 'me',
|
||
'email' => $consultant->email,
|
||
'payone_userid' => 0,
|
||
'clearingtype' => 'cc',
|
||
'active' => true,
|
||
'status' => 2,
|
||
'abo_interval' => 5,
|
||
'next_date' => now()->addDays(3),
|
||
], $overrides));
|
||
}
|
||
|
||
// ─── Provider-Filter Zahlungen ────────────────────────────────────────────────
|
||
|
||
it('filtert Zahlungen nach Provider', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
$stripe = dashboardPayment(['payment_provider' => 'stripe', 'txaction' => 'paid']);
|
||
$payone = dashboardPayment(['payment_provider' => 'payone']);
|
||
|
||
request()->merge(['provider' => 'stripe', 'days' => 7]);
|
||
$view = (new PaymentDashboardController)->payments();
|
||
$ids = $view->getData()['payments']->pluck('id');
|
||
expect($ids->all())->toBe([$stripe->id]);
|
||
|
||
request()->merge(['provider' => 'payone']);
|
||
$view = (new PaymentDashboardController)->payments();
|
||
$ids = $view->getData()['payments']->pluck('id');
|
||
expect($ids->all())->toBe([$payone->id]);
|
||
});
|
||
|
||
it('liefert Umsatz und Zahlungen je Provider in den Payment-Stats', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
dashboardPayment(['payment_provider' => 'stripe', 'txaction' => 'paid', 'amount' => 10000]);
|
||
dashboardPayment(['payment_provider' => 'stripe', 'txaction' => 'failed', 'amount' => 2000]);
|
||
dashboardPayment(['payment_provider' => 'payone', 'txaction' => 'paid', 'amount' => 5000]);
|
||
|
||
$view = (new PaymentDashboardController)->payments();
|
||
$providers = $view->getData()['paymentStats']['providers'];
|
||
|
||
expect($providers['stripe']['total'])->toBe(2)
|
||
->and($providers['stripe']['paid'])->toBe(1)
|
||
->and($providers['stripe']['paid_amount'])->toBe(100.0)
|
||
->and($providers['payone']['paid'])->toBe(1)
|
||
->and($providers['payone']['paid_amount'])->toBe(50.0);
|
||
});
|
||
|
||
// ─── Provider-Filter Transaktionen ────────────────────────────────────────────
|
||
|
||
it('filtert Transaktionen nach Provider und liefert Erfolgsraten je Provider', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
$payment = dashboardPayment(['payment_provider' => 'stripe']);
|
||
$stripeTx = PaymentTransaction::create([
|
||
'shopping_payment_id' => $payment->id, 'provider' => 'stripe',
|
||
'request' => 'paid', 'txaction' => 'paid', 'txid' => 'pi_123', 'userid' => 0,
|
||
]);
|
||
PaymentTransaction::create([
|
||
'shopping_payment_id' => $payment->id, 'provider' => 'payone',
|
||
'request' => 'failed', 'txaction' => 'failed', 'txid' => '99001', 'userid' => 0,
|
||
]);
|
||
|
||
request()->merge(['provider' => 'stripe', 'days' => 7]);
|
||
$view = (new PaymentDashboardController)->transactions();
|
||
$data = $view->getData();
|
||
|
||
expect($data['transactions']->pluck('id')->all())->toBe([$stripeTx->id])
|
||
->and($data['transactionStats']['providers']['stripe']['success_rate'])->toBe(100.0)
|
||
->and($data['transactionStats']['providers']['payone']['failed'])->toBe(1);
|
||
});
|
||
|
||
// ─── Abos je Provider (Auslauf-Fortschritt) ───────────────────────────────────
|
||
|
||
it('zeigt aktive Abos je Provider inkl. Stripe-Anteil und Fehlerzählern', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
dashboardAbo(['payment_provider' => 'stripe']);
|
||
dashboardAbo(['payment_provider' => 'payone']);
|
||
dashboardAbo(['payment_provider' => 'payone', 'status' => 3]);
|
||
dashboardAbo(['payment_provider' => 'stripe', 'status' => 5]); // beendet → zählt nicht
|
||
dashboardAbo(['payment_provider' => 'payone', 'active' => false]); // inaktiv → zählt nicht
|
||
|
||
$view = (new PaymentDashboardController)->index();
|
||
$stats = $view->getData()['aboProviderStats'];
|
||
|
||
expect($stats['total'])->toBe(3)
|
||
->and($stats['providers']['stripe']['total'])->toBe(1)
|
||
->and($stats['providers']['payone']['total'])->toBe(2)
|
||
->and($stats['providers']['payone']['failed'])->toBe(1)
|
||
->and($stats['stripe_share'])->toBe(33.3);
|
||
});
|
||
|
||
it('führt Stripe in den Provider-Status-Karten', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
$view = (new PaymentDashboardController)->index();
|
||
$data = $view->getData();
|
||
|
||
expect($data['providerStats'])->toHaveKey('stripe')
|
||
->and($data['uptimeStats'])->toHaveKey('stripe')
|
||
->and($data['stats'])->toHaveKey('stripe_incidents_30d');
|
||
});
|
||
|
||
// ─── Stripe-Deep-Links ────────────────────────────────────────────────────────
|
||
|
||
it('baut Stripe-Dashboard-Links nur für Stripe-Zahlungen mit PaymentIntent', function () {
|
||
$testMode = dashboardPayment(['payment_provider' => 'stripe', 'external_reference' => 'pi_abc', 'mode' => 'test']);
|
||
$liveMode = dashboardPayment(['payment_provider' => 'stripe', 'external_reference' => 'pi_def', 'mode' => 'live']);
|
||
$sessionOnly = dashboardPayment(['payment_provider' => 'stripe', 'external_reference' => 'cs_xyz']);
|
||
$payone = dashboardPayment(['payment_provider' => 'payone', 'external_reference' => 'pi_fake']);
|
||
|
||
expect($testMode->stripeDashboardUrl())->toBe('https://dashboard.stripe.com/test/payments/pi_abc')
|
||
->and($liveMode->stripeDashboardUrl())->toBe('https://dashboard.stripe.com/payments/pi_def')
|
||
->and($sessionOnly->stripeDashboardUrl())->toBeNull()
|
||
->and($payone->stripeDashboardUrl())->toBeNull();
|
||
});
|
||
|
||
it('baut Stripe-Dashboard-Links für Stripe-Transaktionen über die txid', function () {
|
||
$payment = dashboardPayment(['payment_provider' => 'stripe']);
|
||
|
||
$stripeTx = PaymentTransaction::create([
|
||
'shopping_payment_id' => $payment->id, 'provider' => 'stripe',
|
||
'request' => 'paid', 'txaction' => 'paid', 'txid' => 'pi_tx1', 'userid' => 0, 'mode' => 'live',
|
||
]);
|
||
$sessionTx = PaymentTransaction::create([
|
||
'shopping_payment_id' => $payment->id, 'provider' => 'stripe',
|
||
'request' => 'appointed', 'txaction' => 'appointed', 'txid' => 'cs_tx1', 'userid' => 0,
|
||
]);
|
||
$payoneTx = PaymentTransaction::create([
|
||
'shopping_payment_id' => $payment->id, 'provider' => 'payone',
|
||
'request' => 'paid', 'txaction' => 'paid', 'txid' => '123456', 'userid' => 0,
|
||
]);
|
||
|
||
expect($stripeTx->stripeDashboardUrl())->toBe('https://dashboard.stripe.com/payments/pi_tx1')
|
||
->and($sessionTx->stripeDashboardUrl())->toBeNull()
|
||
->and($payoneTx->stripeDashboardUrl())->toBeNull();
|
||
});
|
||
|
||
// ─── Log-Viewer ───────────────────────────────────────────────────────────────
|
||
|
||
it('unterstützt im Log-Viewer stripe- und payone-Kanäle mit stripe als Default', function () {
|
||
$this->actingAs(dashboardAdmin());
|
||
|
||
$view = (new PaymentDashboardController)->logs();
|
||
expect($view->getData()['selectedChannel'])->toBe('stripe')
|
||
->and($view->getData()['availableChannels'])->toBe(['stripe', 'payone']);
|
||
|
||
request()->merge(['channel' => 'payone']);
|
||
$view = (new PaymentDashboardController)->logs();
|
||
expect($view->getData()['selectedChannel'])->toBe('payone');
|
||
|
||
request()->merge(['channel' => '../etc/passwd']);
|
||
$view = (new PaymentDashboardController)->logs();
|
||
expect($view->getData()['selectedChannel'])->toBe('stripe');
|
||
});
|
||
|
||
// ─── Uptime-Check ─────────────────────────────────────────────────────────────
|
||
|
||
it('prüft die Erreichbarkeit von Stripe und PAYONE im Uptime-Check', function () {
|
||
Http::fake([
|
||
'api.stripe.com/*' => Http::response('', 401),
|
||
'api.pay1.de/*' => Http::response('', 200),
|
||
]);
|
||
|
||
$this->artisan('payment:check-uptime')->assertSuccessful();
|
||
|
||
expect(ProviderUptimeLog::where('provider', 'stripe')->where('is_up', true)->count())->toBe(1)
|
||
->and(ProviderUptimeLog::where('provider', 'payone')->where('is_up', true)->count())->toBe(1);
|
||
});
|