92 lines
4.0 KiB
PHP
92 lines
4.0 KiB
PHP
<?php
|
|
|
|
use App\Services\Stripe\HttpStripeClient;
|
|
use Illuminate\Http\Client\RequestException;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
beforeEach(function () {
|
|
config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)));
|
|
withStripeSecret();
|
|
Http::preventStrayRequests();
|
|
});
|
|
|
|
it('lists only the invoices the customer still owes', function () {
|
|
Http::fake(['api.stripe.com/v1/invoices*' => Http::response(['data' => [
|
|
['id' => 'in_1', 'number' => 'R-1', 'amount_due' => 21480, 'currency' => 'eur', 'created' => 1750000000],
|
|
]])]);
|
|
|
|
$open = app(HttpStripeClient::class)->openInvoices('cus_42');
|
|
|
|
expect($open)->toHaveCount(1)
|
|
->and($open[0]['amount_due_cents'])->toBe(21480)
|
|
->and($open[0]['currency'])->toBe('EUR');
|
|
|
|
// `open` ist Stripes Status für finalisiert und unbezahlt. Ohne den Filter
|
|
// käme die ganze Rechnungshistorie zurück, und der Kunde bekäme einen
|
|
// Bezahlknopf neben Rechnungen, die er längst beglichen hat.
|
|
Http::assertSent(fn ($request) => $request['customer'] === 'cus_42'
|
|
&& $request['status'] === 'open');
|
|
});
|
|
|
|
it('reports a refused charge instead of throwing', function () {
|
|
// Der häufigste Fall überhaupt: die Karte lehnt ab. Das ist kein
|
|
// Serverfehler, das ist die Antwort — und die Seite muss sie zeigen
|
|
// können, statt in eine 500 zu laufen.
|
|
Http::fake(['api.stripe.com/v1/invoices/in_1/pay' => Http::response([
|
|
'error' => ['code' => 'card_declined', 'message' => 'Your card was declined.'],
|
|
], 402)]);
|
|
|
|
$result = app(HttpStripeClient::class)->payInvoice('in_1');
|
|
|
|
expect($result['paid'])->toBeFalse()
|
|
->and($result['failure'])->toContain('declined');
|
|
});
|
|
|
|
it('still throws on an answer that says nothing about the card', function () {
|
|
// 401 oder 500 sind Aussagen über UNS, nicht über das Zahlungsmittel des
|
|
// Kunden. Sie als „Ihre Karte wurde abgelehnt" auszugeben wäre eine
|
|
// Behauptung über sein Konto, hergeleitet aus unserem Fehler.
|
|
Http::fake(['api.stripe.com/v1/invoices/in_1/pay' => Http::response([], 500)]);
|
|
|
|
// Konkrete Klasse, nicht `Throwable::class`: Pest behandelt eine
|
|
// Schnittstelle als zu enthaltende MELDUNG, nicht als Typ — der Test wäre
|
|
// sonst grün, ohne irgendetwas zu prüfen.
|
|
expect(fn () => app(HttpStripeClient::class)->payInvoice('in_1'))
|
|
->toThrow(RequestException::class);
|
|
});
|
|
|
|
it('reports a successful charge', function () {
|
|
Http::fake(['api.stripe.com/v1/invoices/in_1/pay' => Http::response([
|
|
'id' => 'in_1', 'status' => 'paid', 'paid' => true,
|
|
])]);
|
|
|
|
expect(app(HttpStripeClient::class)->payInvoice('in_1')['paid'])->toBeTrue();
|
|
});
|
|
|
|
it('pages to the end instead of stopping at the first hundred', function () {
|
|
// Dieselbe Falle, die diesem Projekt schon einmal teuer kam: der alte
|
|
// Hetzner-Client las nur die erste Seite und legte deshalb einen zweiten
|
|
// A-Eintrag an. `invoiceLines()` direkt daneben blättert längst bis zum
|
|
// Ende. Hier gilt es dem Geld: eine ältere offene Rechnung, die auf Seite
|
|
// zwei liegt, wäre für den Kunden schlicht nicht bezahlbar.
|
|
$seite = fn (int $von, bool $weiter) => Http::response([
|
|
'has_more' => $weiter,
|
|
'data' => collect(range($von, $von + 99))
|
|
->map(fn (int $i) => [
|
|
'id' => 'in_'.$i, 'number' => 'R-'.$i,
|
|
'amount_due' => 100, 'currency' => 'eur', 'created' => 1750000000,
|
|
])->all(),
|
|
]);
|
|
|
|
// Nach der Seite gefragt wird über `starting_after`, nicht über eine
|
|
// Seitenzahl — daran wird hier auch unterschieden, sonst beantwortete die
|
|
// Attrappe beide Aufrufe gleich und der Test liefe gegen den Fehler grün.
|
|
Http::fake(function ($request) use ($seite) {
|
|
parse_str((string) parse_url($request->url(), PHP_URL_QUERY), $query);
|
|
|
|
return isset($query['starting_after']) ? $seite(101, false) : $seite(1, true);
|
|
});
|
|
|
|
expect(app(HttpStripeClient::class)->openInvoices('cus_42'))->toHaveCount(200);
|
|
});
|