CluPilotCloud/tests/Feature/Portal/PaymentMethodTest.php

105 lines
3.9 KiB
PHP

<?php
use App\Livewire\PaymentMethod;
use App\Models\Customer;
use App\Models\User;
use App\Services\Stripe\FakeStripeClient;
use App\Services\Stripe\StripeClient;
use App\Support\StripePublishableKey;
use Livewire\Livewire;
/**
* Bis hierher konnte ein Kunde seine Karte NICHT tauschen — es gab im ganzen
* Portal keine Zahlungsmittel-Verwaltung. Eine abgelaufene Karte war damit
* eine Sackgasse: die Abbuchung scheitert, der Vertrag geht auf `past_due`,
* und der Kunde hat keine Stelle, an der er die Ursache beheben könnte.
*
* Deshalb ist das die ERSTE Lieferung und der Mahnlauf die zweite. Ein
* Mahnlauf über einem Portal ohne Kartenwechsel mahnt jemanden für etwas, das
* er nicht abstellen kann.
*/
function portalUserWithStripe(): User
{
$user = User::factory()->create(['email' => 'karte@portal.test', 'is_admin' => false]);
Customer::factory()->create(['email' => 'karte@portal.test', 'stripe_customer_id' => 'cus_42']);
return $user;
}
beforeEach(function () {
app()->instance(StripeClient::class, new FakeStripeClient);
StripePublishableKey::set('pk_test_sichtbar');
});
it('hands the browser a client secret and the publishable key', function () {
// Am übergebenen Wert geprüft, nicht am HTML: der Schlüssel steht in einem
// `@script`-Block, und den legt Livewire in die Effects-Nutzlast statt in
// das gerenderte Markup. Ein assertSee darauf prüfte die Testfassung von
// Livewire, nicht diese Komponente.
Livewire::actingAs(portalUserWithStripe())
->test(PaymentMethod::class)
->assertSet('clientSecret', 'seti_fake_secret_cus_42')
->assertViewHas('publishableKey', 'pk_test_sichtbar');
});
it('stores the confirmed payment method as the default', function () {
// Der Kern: eine Karte zu hinterlegen genügt nicht. Ohne diesen Schritt
// bucht Stripe weiter mit der kaputten ab, und der Kunde steht im nächsten
// Monat wieder in derselben Mahnung.
$fake = new FakeStripeClient;
app()->instance(StripeClient::class, $fake);
Livewire::actingAs(portalUserWithStripe())
->test(PaymentMethod::class)
->call('confirmed', 'pm_neu123')
->assertHasNoErrors();
expect($fake->defaultPaymentMethods['cus_42'])->toBe('pm_neu123');
});
it('shows which card is on file once one is', function () {
$fake = new FakeStripeClient;
$fake->defaultPaymentMethods['cus_42'] = 'pm_vorhanden';
app()->instance(StripeClient::class, $fake);
Livewire::actingAs(portalUserWithStripe())
->test(PaymentMethod::class)
->assertSee('4242');
});
it('says so instead of drawing a form that never loads', function () {
// Ohne Publishable Key lädt Elements nicht. Ein gezeichnetes, aber totes
// Kartenfeld ist die schlimmste Variante: der Kunde tippt seine Nummer
// ein, nichts passiert, und er hält seine Karte für abgelehnt.
StripePublishableKey::set('');
config()->set('services.stripe.key', '');
config()->set('services.stripe.key_test', '');
Livewire::actingAs(portalUserWithStripe())
->test(PaymentMethod::class)
->assertSet('clientSecret', null)
->assertSee(__('billing.card_unavailable'));
});
it('refuses a payment method id that is not one', function () {
// `confirmed()` wird aus dem Browser gerufen. Ohne diese Prüfung wäre das
// ein Weg, einen fremden Bezeichner an das eigene Stripe-Konto zu hängen.
$fake = new FakeStripeClient;
app()->instance(StripeClient::class, $fake);
Livewire::actingAs(portalUserWithStripe())
->test(PaymentMethod::class)
->call('confirmed', 'nicht-eine-pm-id')
->assertHasErrors();
expect($fake->defaultPaymentMethods)->toBe([]);
});
it('is not reachable for a visitor without a customer record', function () {
$fremder = User::factory()->create(['email' => 'ohne@portal.test', 'is_admin' => false]);
Livewire::actingAs($fremder)
->test(PaymentMethod::class)
->assertStatus(404);
});