- VatResolver::normalizeCountryCode()/isEuCountry() als Single Source of Truth;
resolve(), VatIdValidationService und StripeCheckoutService nutzen jetzt die
zentrale EU-Pruefung statt eigener config-Lookups.
- User::findByEmail() buendelt die LOWER(email)-Lookups (ContactAccessService,
SocialAuthService). Bleibt bewusst case-insensitiv, da E-Mails nicht
normalisiert gespeichert werden; der indexfreundliche Direktvergleich
braucht erst eine Schreib-Normalisierung (offen, dokumentiert).
- config('billing.default_currency') ersetzt 8 verstreute 'EUR'/'eur'-Literale
(Stripe-Calls via strtolower). Vereinheitlicht nebenbei die zuvor uneinheitlich
gespeicherte SinglePurchase.currency.
- Neuer CustomerAccountProvisioner::provisionVerifiedCustomer() ist die
gemeinsame Provisioning-Methode (verifiziert + aktiv + customer-Rolle) fuer
ContactAccessService und SocialAuthService.
- Tests: UserFindByEmailTest, erweiterte VatResolverTest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
2.4 KiB
PHP
49 lines
2.4 KiB
PHP
<?php
|
|
|
|
use App\Enums\VatTreatment;
|
|
use App\Services\Billing\VatResolver;
|
|
|
|
test('vat treatment follows the 12.06. decision rules', function (?string $country, ?string $vatId, VatTreatment $expected) {
|
|
expect(app(VatResolver::class)->resolve($country, $vatId))->toBe($expected);
|
|
})->with([
|
|
'Deutschland immer mit Steuer' => ['DE', null, VatTreatment::Domestic],
|
|
'Deutschland auch mit USt-ID mit Steuer' => ['DE', 'DE123456789', VatTreatment::Domestic],
|
|
'EU mit gültiger USt-ID → Reverse Charge' => ['AT', 'ATU12345678', VatTreatment::ReverseCharge],
|
|
'EU ohne USt-ID → mit Steuer' => ['AT', null, VatTreatment::EuConsumer],
|
|
'EU mit fremdländischer USt-ID → mit Steuer' => ['AT', 'DE123456789', VatTreatment::EuConsumer],
|
|
'Griechenland mit EL-Präfix' => ['GR', 'EL123456789', VatTreatment::ReverseCharge],
|
|
'Drittland grundsätzlich befreit' => ['CH', null, VatTreatment::ThirdCountry],
|
|
'Drittland auch mit ID befreit' => ['US', 'US-TAX-1', VatTreatment::ThirdCountry],
|
|
'Fehlendes Land wie Inland behandeln' => [null, null, VatTreatment::Domestic],
|
|
]);
|
|
|
|
test('normalizeCountryCode uppercases and tolerates null', function () {
|
|
$resolver = app(VatResolver::class);
|
|
|
|
expect($resolver->normalizeCountryCode('at'))->toBe('AT')
|
|
->and($resolver->normalizeCountryCode(null))->toBe('')
|
|
->and($resolver->normalizeCountryCode('De'))->toBe('DE');
|
|
});
|
|
|
|
test('isEuCountry recognises EU members and excludes DE and third countries', function () {
|
|
$resolver = app(VatResolver::class);
|
|
|
|
expect($resolver->isEuCountry('AT'))->toBeTrue()
|
|
->and($resolver->isEuCountry('DE'))->toBeFalse()
|
|
->and($resolver->isEuCountry('CH'))->toBeFalse();
|
|
});
|
|
|
|
test('tax cents are derived from the treatment', function () {
|
|
$resolver = app(VatResolver::class);
|
|
|
|
expect($resolver->taxCentsFor(16723, VatTreatment::Domestic))->toBe(3177);
|
|
expect($resolver->taxCentsFor(16723, VatTreatment::EuConsumer))->toBe(3177);
|
|
expect($resolver->taxCentsFor(16723, VatTreatment::ReverseCharge))->toBe(0);
|
|
expect($resolver->taxCentsFor(16723, VatTreatment::ThirdCountry))->toBe(0);
|
|
});
|
|
|
|
test('exempt treatments carry a legal tax note', function () {
|
|
expect(VatTreatment::ReverseCharge->taxNote())->toContain('Reverse Charge');
|
|
expect(VatTreatment::ThirdCountry->taxNote())->toContain('Nicht im Inland steuerbar');
|
|
expect(VatTreatment::Domestic->taxNote())->toBeNull();
|
|
});
|