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:
parent
036a53499f
commit
afcca34f91
25 changed files with 905 additions and 140 deletions
|
|
@ -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]);
|
||||
|
|
|
|||
94
tests/Feature/Billing/VatIdValidationServiceTest.php
Normal file
94
tests/Feature/Billing/VatIdValidationServiceTest.php
Normal 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);
|
||||
});
|
||||
|
|
@ -32,12 +32,14 @@ test('customer can update own profile fields', function () {
|
|||
->set('language', 'en')
|
||||
->set('address', 'Musterfirma GmbH, Musterstrasse 1, 10115 Berlin')
|
||||
->set('countryCode', 'AT')
|
||||
->set('billingName', 'Musterfirma GmbH')
|
||||
->set('billingCompany', 'Musterfirma GmbH')
|
||||
->set('billingFirstName', 'Max')
|
||||
->set('billingLastName', 'Mustermann')
|
||||
->set('billingAddress1', 'Musterstrasse 1')
|
||||
->set('billingPostalCode', '10115')
|
||||
->set('billingCity', 'Berlin')
|
||||
->set('billingCountryCode', 'DE')
|
||||
->set('taxIdNumber', 'ATU12345678')
|
||||
->set('taxIdNumber', 'DE123456789')
|
||||
->set('showStats', true)
|
||||
->call('saveProfile')
|
||||
->assertHasNoErrors();
|
||||
|
|
@ -50,13 +52,59 @@ test('customer can update own profile fields', function () {
|
|||
expect($customer->profile?->last_name)->toBe('Mustermann');
|
||||
expect($customer->profile?->address)->toBe('Musterfirma GmbH, Musterstrasse 1, 10115 Berlin');
|
||||
expect($customer->profile?->country_code)->toBe('AT');
|
||||
expect($customer->profile?->tax_id_number)->toBe('ATU12345678');
|
||||
expect($customer->profile?->tax_id_number)->toBe('DE123456789');
|
||||
expect($customer->profile?->show_stats)->toBeTrue();
|
||||
expect($customer->billingAddress?->name)->toBe('Musterfirma GmbH');
|
||||
expect($customer->billingAddress?->company)->toBe('Musterfirma GmbH');
|
||||
expect($customer->billingAddress?->first_name)->toBe('Max');
|
||||
expect($customer->billingAddress?->last_name)->toBe('Mustermann');
|
||||
expect($customer->billingAddress?->name)->toBe('Max Mustermann');
|
||||
expect($customer->billingAddress?->address1)->toBe('Musterstrasse 1');
|
||||
expect($customer->billingAddress?->postal_code)->toBe('10115');
|
||||
expect($customer->billingAddress?->city)->toBe('Berlin');
|
||||
expect($customer->billingAddress?->country_code)->toBe('DE');
|
||||
expect($customer->billingAddress?->vat_id)->toBe('DE123456789');
|
||||
});
|
||||
|
||||
test('an incomplete billing address reports each missing field exactly once', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('billingCompany', 'Nur Firma GmbH')
|
||||
->call('saveProfile')
|
||||
->assertHasErrors(['billingLastName', 'billingAddress1', 'billingPostalCode', 'billingCity']);
|
||||
});
|
||||
|
||||
test('a malformed vat id blocks saving with a field error', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('taxIdNumber', '123-nicht-gueltig')
|
||||
->call('saveProfile')
|
||||
->assertHasErrors(['taxIdNumber']);
|
||||
});
|
||||
|
||||
test('the profile data can be copied into the billing address', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->set('salutationKey', 'mr')
|
||||
->set('firstName', 'Kopier')
|
||||
->set('lastName', 'Kunde')
|
||||
->set('countryCode', 'AT')
|
||||
->call('copyProfileToBilling')
|
||||
->assertSet('billingSalutationKey', 'mr')
|
||||
->assertSet('billingFirstName', 'Kopier')
|
||||
->assertSet('billingLastName', 'Kunde')
|
||||
->assertSet('billingCountryCode', 'AT');
|
||||
});
|
||||
|
||||
test('customer profile keeps company management out of the profile page', function () {
|
||||
|
|
@ -72,7 +120,7 @@ test('customer profile keeps company management out of the profile page', functi
|
|||
|
||||
LivewireVolt::test('customer.profile')
|
||||
->assertSee('Rechnungsadresse')
|
||||
->assertSee('Profileinstellungen')
|
||||
->assertSee('Persönliche Daten')
|
||||
->assertSee('Firmen verwalten')
|
||||
->assertDontSee('Zugeordnete Firmen')
|
||||
->assertDontSee('Nicht im Profil geladene Firma');
|
||||
|
|
|
|||
34
tests/Feature/PressReleaseCreateCompanyGuardTest.php
Normal file
34
tests/Feature/PressReleaseCreateCompanyGuardTest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Volt as LivewireVolt;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('the pm create page shows a notice instead of the form without a company', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->assertSet('hasCompanies', false)
|
||||
->assertSee('Ohne Firma kann keine Pressemitteilung angelegt werden.')
|
||||
->assertSee('Firma anlegen')
|
||||
->assertDontSee('Zur Prüfung senden');
|
||||
});
|
||||
|
||||
test('the pm create page shows the editor when a company exists', function () {
|
||||
/** @var TestCase $this */
|
||||
$customer = User::factory()->create(['is_active' => true]);
|
||||
$company = Company::factory()->presseecho()->create();
|
||||
$customer->companies()->attach($company->id, ['role' => 'owner']);
|
||||
|
||||
$this->actingAs($customer);
|
||||
|
||||
LivewireVolt::test('customer.press-releases.create')
|
||||
->assertSet('hasCompanies', true)
|
||||
->assertSet('companyId', $company->id)
|
||||
->assertDontSee('Ohne Firma kann keine Pressemitteilung angelegt werden.')
|
||||
->assertSee('Zur Prüfung senden');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue