'CluPilot Cloud e.U.', 'address' => 'Dreherstraße 66/1/8', 'postcode' => '1110', 'city' => 'Wien', 'vat_id' => 'ATU00000000', ]); }); /** A portal customer with a signed-in user behind them. */ function invoiceCustomer(string $email): array { $user = User::factory()->create(['email' => $email, 'email_verified_at' => now()]); $customer = Customer::factory()->create(['email' => $email, 'user_id' => $user->id]); return [$user, $customer]; } function issuedFor(Customer $customer, int $chargedCents): Invoice { return app(IssueInvoice::class)->forOrders($customer, collect([ Order::factory()->create([ 'customer_id' => $customer->id, 'amount_cents' => $chargedCents, 'currency' => 'EUR', 'status' => 'paid', 'stripe_event_id' => 'cs_'.uniqid(), ]), ])); } it('lists the invoices this customer was actually issued, with their real numbers and totals', function () { [$user, $customer] = invoiceCustomer('own@invoices.test'); $older = issuedFor($customer, 21480); $newer = issuedFor($customer, 23880); Livewire::actingAs($user)->test(Invoices::class) ->assertSee($older->number) ->assertSee($newer->number) // The number the customer was charged, off the document itself. ->assertSee('238,80') ->assertSee('214,80') // Newest first: the one somebody came here to fetch is the last one. ->assertViewHas('invoices', fn ($invoices) => $invoices->first()->is($newer)) // And nothing invented. These were the mock's, and its prefix belongs to // no configured series. ->assertDontSee('CP-2026-0007') ->assertDontSee('01.08.2026'); }); it('never shows one customer another customer\'s invoice', function () { [$user] = invoiceCustomer('mine@invoices.test'); [, $stranger] = invoiceCustomer('theirs@invoices.test'); $theirs = issuedFor($stranger, 21480); Livewire::actingAs($user)->test(Invoices::class) ->assertDontSee($theirs->number) ->assertSee(__('invoices.empty')); }); it('hands over the customer\'s own PDF and refuses everybody else\'s', function () { [$user, $customer] = invoiceCustomer('pdf@invoices.test'); [$stranger] = invoiceCustomer('nosy@invoices.test'); $invoice = issuedFor($customer, 21480); $this->actingAs($user)->get(route('invoices.pdf', $invoice->uuid)) ->assertOk() ->assertHeader('content-type', 'application/pdf'); // Checked in the query, not by leaving the link off the page: a URL anybody // can type is not made private by not printing it. $this->actingAs($stranger)->get(route('invoices.pdf', $invoice->uuid))->assertNotFound(); }); it('does not hand an invoice to somebody who is not signed in', function () { [, $customer] = invoiceCustomer('guarded@invoices.test'); $invoice = issuedFor($customer, 21480); $this->get(route('invoices.pdf', $invoice->uuid))->assertRedirect('/login'); }); it('says nothing at all about a next charge nobody has computed', function () { [$user, $customer] = invoiceCustomer('nofuture@invoices.test'); issuedFor($customer, 21480); // The mock announced "Nächste Abbuchung 198 € am 01.08.2026" to every // customer on the platform. What Stripe will take next cycle depends on // prorations, account credit and discounts, and nothing here works it out — // so the page says nothing rather than guessing. $html = Livewire::actingAs($user)->test(Invoices::class)->html(); expect($html)->not->toContain('Nächste Abbuchung') ->and($html)->not->toContain('Ausgaben'); }); it('marks a cancellation so it cannot be mistaken for a second charge', function () { [$user, $customer] = invoiceCustomer('storno@invoices.test'); $original = issuedFor($customer, 21480); $storno = app(IssueInvoice::class)->cancelling($original); Livewire::actingAs($user)->test(Invoices::class) ->assertSee($storno->number) ->assertSee(__('invoices.storno')); });