Skip to content

Commit

Permalink
EU One-Stop-Shop Tax Return (#84)
Browse files Browse the repository at this point in the history
* Simpler update for EU tax

* Capture new OSS behaviour

* Update composer.json

* No need to fork

* Remove branch alias in preparation for release of 1.0

* Add PHP 8.3 to tests
  • Loading branch information
dmnc authored Jun 18, 2024
1 parent ba15124 commit 646977f
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 11 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/phpunit83.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI PHP 8.3

on: [push]

jobs:
build-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- uses: php-actions/composer@v6
with:
php_version: "8.3"

- name: PHPUnit Tests
uses: php-actions/phpunit@v3
with:
version: "8"
php_version: "8.3"
7 changes: 1 addition & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,5 @@
{
"name": "David Kitchen"
}
],
"extra": {
"branch-alias": {
"dev-master": "0.x-dev"
}
}
]
}
24 changes: 21 additions & 3 deletions src/Resolver/TaxType/EuTaxTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
*/
class EuTaxTypeResolver implements TaxTypeResolverInterface
{
/**
* The date on which the one stop shop rules are in effect
*/
const ONE_STOP_SHOP_INTRODUCTION = '2020-07-01T00:00:00+0100';

use StoreRegistrationCheckerTrait;

/**
Expand Down Expand Up @@ -60,19 +65,32 @@ public function resolve(TaxableInterface $taxable, Context $context)
// to Germany needs to have German VAT applied.
$isDigital = $context->getDate()->format('Y') >= '2015' && !$taxable->isPhysical();

$oneStopShop = $context->getDate()->getTimeStamp() > strtotime(static::ONE_STOP_SHOP_INTRODUCTION);

$resolvedTaxTypes = [];
if (empty($storeTaxTypes) && !empty($storeRegistrationTaxTypes)) {
if (
!$oneStopShop
&& empty($storeTaxTypes)
&& !empty($storeRegistrationTaxTypes)
) {
// The store is not in the EU but is registered to collect VAT.
// This VAT is only charged on B2C digital services.
$resolvedTaxTypes = self::NO_APPLICABLE_TAX_TYPE;
if ($isDigital && !$customerTaxNumber) {
$resolvedTaxTypes = $customerTaxTypes;
}
} elseif ($customerTaxNumber && $customerCountry != $storeCountry) {
} elseif (
$customerTaxNumber
&& $customerCountry != $storeCountry
&& !empty($storeTaxTypes) // store is in the EU
) {
// Intra-community supply (B2B).
$icTaxType = $this->taxTypeRepository->get('eu_ic_vat');
$resolvedTaxTypes = [$icTaxType];
} elseif ($isDigital) {
} elseif (
$isDigital
|| $oneStopShop
) {
$resolvedTaxTypes = $customerTaxTypes;
} else {
// Physical products use the origin tax types, unless the store is
Expand Down
11 changes: 9 additions & 2 deletions tests/Resolver/TaxType/EuTaxTypeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ public function dataProvider()

$date1 = new \DateTime('2014-02-24');
$date2 = new \DateTime('2015-02-24');
$date3 = new \DateTime('2021-08-24');
$notApplicable = EuTaxTypeResolver::NO_APPLICABLE_TAX_TYPE;

return [
Expand All @@ -249,9 +250,9 @@ public function dataProvider()
// French customer, French store, VAT number provided.
[$physicalTaxable, $this->getContext($frenchAddress, $frenchAddress, '123'), 'fr_vat'],
// German customer, French store, physical product.
[$physicalTaxable, $this->getContext($germanAddress, $frenchAddress), 'fr_vat'],
[$physicalTaxable, $this->getContext($germanAddress, $frenchAddress, '', [], $date2), 'fr_vat'],
// German customer, French store registered for German VAT, physical product.
[$physicalTaxable, $this->getContext($germanAddress, $frenchAddress, '', ['DE']), 'de_vat'],
[$physicalTaxable, $this->getContext($germanAddress, $frenchAddress, '', ['DE'], $date2), 'de_vat'],
// German customer, French store, digital product before Jan 1st 2015.
[$digitalTaxable, $this->getContext($germanAddress, $frenchAddress, '', [], $date1), 'fr_vat'],
// German customer, French store, digital product.
Expand All @@ -266,6 +267,12 @@ public function dataProvider()
[$physicalTaxable, $this->getContext($serbianAddress, $frenchAddress), []],
// French customer, Serbian store, physical product.
[$physicalTaxable, $this->getContext($frenchAddress, $serbianAddress), []],
// German customer, French store, digital product after July 1st 2021.
[$digitalTaxable, $this->getContext($germanAddress, $frenchAddress, '', [], $date3), 'de_vat'],
// German customer, French store, physical product after July 1st 2021.
[$physicalTaxable, $this->getContext($germanAddress, $frenchAddress, '', [], $date3), 'de_vat'],
// German customer US store registered in FR, physical product after July 1st 2021
[$physicalTaxable, $this->getContext($germanAddress, $usAddress, '', ['FR'], $date3), 'de_vat'],
];
}

Expand Down

0 comments on commit 646977f

Please sign in to comment.