User-Panel-Restarbeiten: PM-Guard, Profil-Rework, USt-ID-Prüfung, Buchungspflicht-Adresse

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-12 14:36:18 +00:00
parent 036a53499f
commit afcca34f91
25 changed files with 905 additions and 140 deletions

View file

@ -2,6 +2,7 @@
use App\Enums\SinglePurchaseStatus;
use App\Enums\SinglePurchaseType;
use App\Models\BillingAddress;
use App\Models\Plan;
use App\Models\SinglePurchase;
use App\Models\User;
@ -21,6 +22,9 @@ function checkoutTestCustomer(): User
$user = User::factory()->create();
$user->assignRole('customer');
// Buchungs-Voraussetzung (12.06.2026): vollständige Rechnungsadresse.
BillingAddress::factory()->create(['user_id' => $user->id]);
return $user;
}
@ -55,6 +59,26 @@ test('an unknown plan or invalid interval responds 404', function () {
$this->get("/admin/me/checkout/abo/{$plan->slug}/weekly")->assertNotFound();
});
test('a checkout without a complete billing address redirects to the profile', function () {
/** @var TestCase $this */
$user = User::factory()->create();
$user->assignRole('customer');
$plan = Plan::factory()->create(['stripe_price_id_monthly' => 'price_test_m_addr']);
config()->set('billing.single_pm_stripe_price_id', 'price_test_single_pm');
$this->actingAs($user)
->get(route('me.checkout.subscription', ['planSlug' => $plan->slug, 'interval' => 'monthly']))
->assertRedirect(route('me.profile'))
->assertSessionHas('checkout-notice');
$this->actingAs($user)
->get(route('me.checkout.single-pm'))
->assertRedirect(route('me.profile'))
->assertSessionHas('checkout-notice');
expect(SinglePurchase::count())->toBe(0);
});
test('a plan without a synced stripe price redirects back with a notice', function () {
/** @var TestCase $this */
$plan = Plan::factory()->create(['stripe_price_id_monthly' => null]);

View file

@ -0,0 +1,94 @@
<?php
use App\Enums\VatIdCheckStatus;
use App\Services\Billing\VatIdValidationService;
use Illuminate\Support\Facades\Http;
function vatIdService(): VatIdValidationService
{
return app(VatIdValidationService::class);
}
test('a malformed vat id fails the format check', function () {
$result = vatIdService()->check('123-nicht-gueltig');
expect($result['status'])->toBe(VatIdCheckStatus::FormatInvalid);
});
test('a vat id must match the billing country', function () {
$result = vatIdService()->check('ATU12345678', 'DE');
expect($result['status'])->toBe(VatIdCheckStatus::FormatInvalid)
->and($result['message'])->toContain('passt nicht zum Land');
});
test('greek vat ids use the EL prefix and pass for country GR', function () {
$result = vatIdService()->check('EL123456789', 'GR');
expect($result['status'])->not->toBe(VatIdCheckStatus::FormatInvalid);
});
test('german vat ids are format checked but not confirmed online', function () {
config()->set('billing.own_vat_id', 'DE999999999');
Http::fake();
$result = vatIdService()->check('DE123456789', 'DE');
expect($result['status'])->toBe(VatIdCheckStatus::Unverified);
Http::assertNothingSent();
});
test('without an own vat id the check stays a format check', function () {
config()->set('billing.own_vat_id', null);
Http::fake();
$result = vatIdService()->check('ATU12345678', 'AT');
expect($result['status'])->toBe(VatIdCheckStatus::Unverified)
->and($result['message'])->toContain('nicht konfiguriert');
Http::assertNothingSent();
});
test('a foreign eu vat id is confirmed via the evatr api', function () {
config()->set('billing.own_vat_id', 'DE999999999');
Http::fake([
'api.evatr.vies.bzst.de/*' => Http::response(['status' => 'evatr-0000']),
]);
$result = vatIdService()->check('ATU12345678', 'AT');
expect($result['status'])->toBe(VatIdCheckStatus::Valid);
Http::assertSent(function ($request): bool {
return $request['anfragendeUstid'] === 'DE999999999'
&& $request['angefragteUstid'] === 'ATU12345678';
});
});
test('a rejected evatr status marks the vat id as invalid', function () {
config()->set('billing.own_vat_id', 'DE999999999');
Http::fake([
'api.evatr.vies.bzst.de/*' => Http::response(['status' => 'evatr-1001']),
]);
$result = vatIdService()->check('FRXX999999999', 'FR');
expect($result['status'])->toBe(VatIdCheckStatus::Invalid)
->and($result['message'])->toContain('evatr-1001');
});
test('an unreachable evatr api degrades to unverified', function () {
config()->set('billing.own_vat_id', 'DE999999999');
Http::fake([
'api.evatr.vies.bzst.de/*' => Http::response(null, 503),
]);
$result = vatIdService()->check('NL123456789B01', 'NL');
expect($result['status'])->toBe(VatIdCheckStatus::Unverified);
});