mivita/tests/Feature/PaymentDashboard/PaymentDashboardProviderTest.php
Kevin 8e09aaefe8 Stripe-Integration (S7.4): Payment-Dashboard für Stripe
- 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>
2026-07-10 16:57:50 +00:00

220 lines
9.5 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.

<?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);
});