instance(VatIdVerifier::class, $fake); return $fake; } function businessWithVatId(string $vatId): Customer { return Customer::factory()->create([ 'customer_type' => Customer::TYPE_BUSINESS, 'vat_id' => $vatId, ]); } it('records a number the register knows, and reverse charge then applies', function () { armRegister()->registered('NL123456789B01', 'Berger B.V.'); $customer = businessWithVatId('NL123456789B01'); app(VerifyCustomerVatId::class)($customer); expect($customer->fresh()->hasVerifiedVatId())->toBeTrue() // Proven through the tax rule, not through the column: the column is only // interesting because of what TaxTreatment does with it. ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeTrue() ->and(TaxTreatment::for($customer->fresh())->rate)->toBe(0.0); }); it('refuses a number the register does not know, and keeps charging VAT', function () { armRegister()->notRegistered('NL999999999B99'); $customer = businessWithVatId('NL999999999B99'); $check = app(VerifyCustomerVatId::class)($customer); expect($check->isInvalid())->toBeTrue() ->and($customer->fresh()->hasVerifiedVatId())->toBeFalse() ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeFalse(); }); it('changes nothing at all when the register is unreachable', function () { // The one that matters. An outage must not clear a verification that has been // standing for months and quietly move that customer onto the domestic rate — // their next invoice would say something different because a national register // was asleep. $customer = businessWithVatId('NL123456789B01'); armRegister()->registered('NL123456789B01'); app(VerifyCustomerVatId::class)($customer); $stampedAt = $customer->fresh()->vat_id_verified_at; armRegister()->down('NL123456789B01'); $check = app(VerifyCustomerVatId::class)($customer->fresh()); expect($check->isUnavailable())->toBeTrue() ->and($customer->fresh()->hasVerifiedVatId())->toBeTrue() ->and($customer->fresh()->vat_id_verified_at->equalTo($stampedAt))->toBeTrue() ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeTrue(); }); it('withdraws reverse charge once the register stops knowing the number', function () { // A registration can be revoked. From that moment we are zero-rating against // a number that does not exist, which is our liability, not the customer's. $customer = businessWithVatId('NL123456789B01'); armRegister()->registered('NL123456789B01'); app(VerifyCustomerVatId::class)($customer); armRegister()->notRegistered('NL123456789B01'); app(VerifyCustomerVatId::class)($customer->fresh()); expect($customer->fresh()->hasVerifiedVatId())->toBeFalse() ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeFalse(); }); it('drops the verification when the number is removed', function () { $customer = businessWithVatId('NL123456789B01'); armRegister()->registered('NL123456789B01'); app(VerifyCustomerVatId::class)($customer); $customer->update(['vat_id' => null]); $check = app(VerifyCustomerVatId::class)($customer->fresh()); expect($check)->toBeNull() ->and($customer->fresh()->vat_id_verified_at)->toBeNull() ->and($customer->fresh()->vat_id_verified_value)->toBeNull(); }); it('never asks the register about a number no member state could hold', function () { $fake = armRegister(); $customer = businessWithVatId('not-a-number'); $check = app(VerifyCustomerVatId::class)($customer); expect($check->isInvalid())->toBeTrue() // Not one request spent on something that cannot be a VAT number. ->and($fake->asked)->toBeEmpty(); }); it('still charges domestic VAT to a business in our own country', function () { // Reverse charge is a cross-border rule. An Austrian business pays Austrian // VAT and reclaims it as input tax like any other Austrian buyer. armRegister()->registered('ATU12345678'); $customer = businessWithVatId('ATU12345678'); app(VerifyCustomerVatId::class)($customer); expect($customer->fresh()->hasVerifiedVatId())->toBeTrue() ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeFalse() ->and(TaxTreatment::for($customer->fresh())->rate)->toBeGreaterThan(0.0); }); it('still charges VAT to somebody who has recorded themselves as a private person', function () { armRegister()->registered('NL123456789B01'); $customer = Customer::factory()->create([ 'customer_type' => Customer::TYPE_CONSUMER, 'vat_id' => 'NL123456789B01', ]); app(VerifyCustomerVatId::class)($customer); // The number is real — it is just not theirs to zero the tax with. expect($customer->fresh()->hasVerifiedVatId())->toBeTrue() ->and(TaxTreatment::for($customer->fresh())->reverseCharge)->toBeFalse(); }); it('asks about every number on record and touches nothing in a dry run', function () { armRegister()->registered('NL123456789B01'); $customer = businessWithVatId('NL123456789B01'); $this->artisan('clupilot:verify-vat-ids --dry-run')->assertSuccessful(); expect($customer->fresh()->hasVerifiedVatId())->toBeFalse(); $this->artisan('clupilot:verify-vat-ids')->assertSuccessful(); expect($customer->fresh()->hasVerifiedVatId())->toBeTrue(); });