-
-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the
CalculateTaxes
listener to cart update and shipping addre…
…ss change events
- Loading branch information
1 parent
483392f
commit 20acf2f
Showing
6 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* Contains the CalculateTaxes class. | ||
* | ||
* @copyright Copyright (c) 2024 Vanilo UG | ||
* @author Attila Fulop | ||
* @license MIT | ||
* @since 2024-01-30 | ||
* | ||
*/ | ||
|
||
namespace Vanilo\Foundation\Listeners; | ||
|
||
use Illuminate\Support\Arr; | ||
use Vanilo\Adjustments\Contracts\Adjustable; | ||
use Vanilo\Adjustments\Contracts\Adjustment; | ||
use Vanilo\Adjustments\Models\AdjustmentTypeProxy; | ||
use Vanilo\Cart\Contracts\CartEvent; | ||
use Vanilo\Checkout\Contracts\CheckoutEvent; | ||
use Vanilo\Checkout\Facades\Checkout; | ||
use Vanilo\Foundation\Models\CartItem; | ||
use Vanilo\Support\Dto\DetailedAmount; | ||
use Vanilo\Taxes\Contracts\TaxRateResolver; | ||
|
||
class CalculateTaxes | ||
{ | ||
public function __construct( | ||
protected ?TaxRateResolver $rateResolver, | ||
) { | ||
} | ||
|
||
public function handle(CheckoutEvent|CartEvent $event): void | ||
{ | ||
if ($event instanceof CheckoutEvent) { | ||
$checkout = $event->getCheckout(); | ||
$cart = $checkout->getCart(); | ||
} else { | ||
$cart = $event->getCart(); | ||
Checkout::setCart($cart); | ||
$checkout = Checkout::getFacadeRoot(); | ||
} | ||
|
||
if (!$cart instanceof Adjustable) { | ||
return; | ||
} | ||
|
||
$cart->adjustments()->deleteByType(AdjustmentTypeProxy::TAX()); | ||
|
||
if (null !== $this->rateResolver) { | ||
$taxes = []; | ||
/** @var CartItem $item */ | ||
foreach ($cart->getItems() as $item) { | ||
if ($rate = $this->rateResolver->findTaxRate($item)) { | ||
$calculator = $rate->getCalculator(); | ||
if ($adjuster = $calculator->getAdjuster($rate->configuration())) { | ||
/** @var Adjustment|null $adjustment */ | ||
if ($adjustment = $cart->adjustments()?->create($adjuster)) { | ||
$taxes[$adjustment->getTitle()] = ($taxes[$adjustment->getTitle()] ?? 0) + $adjustment->getAmount(); | ||
} | ||
} | ||
} | ||
} | ||
$checkout->setTaxesAmount( | ||
DetailedAmount::fromArray( | ||
Arr::mapWithKeys($taxes, fn($amount, $title) => [['title' => $title, 'amount' => $amount]]), | ||
), | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* Contains the ExampleTaxCalculator class. | ||
* | ||
* @copyright Copyright (c) 2024 Vanilo UG | ||
* @author Attila Fulop | ||
* @license MIT | ||
* @since 2024-02-26 | ||
* | ||
*/ | ||
|
||
namespace Vanilo\Foundation\Tests\Examples; | ||
|
||
use Nette\Schema\Expect; | ||
use Nette\Schema\Schema; | ||
use Vanilo\Adjustments\Adjusters\SimpleTax; | ||
use Vanilo\Contracts\DetailedAmount; | ||
use Vanilo\Taxes\Contracts\TaxCalculator; | ||
|
||
class ExampleTaxCalculator implements TaxCalculator | ||
{ | ||
public static function getName(): string | ||
{ | ||
return 'Example'; | ||
} | ||
|
||
public function getAdjuster(?array $configuration = null): ?object | ||
{ | ||
$rate = floatval($configuration['rate'] ?? 0); | ||
$adjuster = new SimpleTax($rate, false); | ||
$adjuster->setTitle("$rate%"); | ||
|
||
return $adjuster; | ||
} | ||
|
||
public function calculate(?object $subject = null, ?array $configuration = null): DetailedAmount | ||
{ | ||
$rate = floatval($configuration['rate'] ?? 0); | ||
|
||
return \Vanilo\Support\Dto\DetailedAmount::fromArray([['title' => "$rate%", 'amount' => $subject->itemsTotal() * $rate / 100]]); | ||
} | ||
|
||
public function getSchema(): Schema | ||
{ | ||
return Expect::structure(['rate' => Expect::float(0)->required()]); | ||
} | ||
|
||
public function getSchemaSample(array $mergeWith = null): array | ||
{ | ||
return ['rate' => 19]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* Contains the ExampleTaxEngine class. | ||
* | ||
* @copyright Copyright (c) 2024 Vanilo UG | ||
* @author Attila Fulop | ||
* @license MIT | ||
* @since 2024-02-26 | ||
* | ||
*/ | ||
|
||
namespace Vanilo\Foundation\Tests\Examples; | ||
|
||
use Vanilo\Contracts\Address; | ||
use Vanilo\Taxes\Contracts\Taxable; | ||
use Vanilo\Taxes\Contracts\TaxRate; | ||
use Vanilo\Taxes\Contracts\TaxRateResolver; | ||
use Vanilo\Taxes\Models\TaxCategoryType; | ||
|
||
/** | ||
* This is an example tax rate resolver | ||
* dedicated for unit-test only with | ||
* the following hard-coded logic | ||
* - physical products: 19% | ||
* - shipping: 7% | ||
* - everything else: 15% | ||
*/ | ||
class ExampleTaxEngine implements TaxRateResolver | ||
{ | ||
public const ID = 'example'; | ||
|
||
public function findTaxRate(Taxable $taxable, ?Address $billingAddress = null, ?Address $shippingAddress = null): ?TaxRate | ||
{ | ||
$rate = match ($taxable->getTaxCategory()->getType()->value()) { | ||
TaxCategoryType::PHYSICAL_GOODS => 19, | ||
TaxCategoryType::TRANSPORT_SERVICES => 7, | ||
default => 15, | ||
}; | ||
|
||
return \Vanilo\Taxes\Models\TaxRate::firstOrCreate([ | ||
'rate' => $rate, | ||
'name' => "$rate%", | ||
'calculator' => 'example', | ||
'configuration' => ['rate' => $rate], | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* Contains the TaxCalculationTest class. | ||
* | ||
* @copyright Copyright (c) 2024 Vanilo UG | ||
* @author Attila Fulop | ||
* @license MIT | ||
* @since 2024-02-26 | ||
* | ||
*/ | ||
|
||
namespace Vanilo\Foundation\Tests; | ||
|
||
use Vanilo\Adjustments\Contracts\AdjustmentCollection; | ||
use Vanilo\Adjustments\Models\AdjustmentType; | ||
use Vanilo\Cart\Facades\Cart; | ||
use Vanilo\Checkout\Facades\Checkout; | ||
use Vanilo\Foundation\Models\Product; | ||
use Vanilo\Foundation\Tests\Examples\ExampleTaxCalculator; | ||
use Vanilo\Foundation\Tests\Examples\ExampleTaxEngine; | ||
use Vanilo\Taxes\Facades\TaxEngine; | ||
use Vanilo\Taxes\Models\TaxCategory; | ||
use Vanilo\Taxes\Models\TaxCategoryType; | ||
use Vanilo\Taxes\Resolver\TaxEngineManager; | ||
use Vanilo\Taxes\TaxCalculators; | ||
|
||
class TaxCalculationTest extends TestCase | ||
{ | ||
protected function setUp(): void | ||
{ | ||
parent::setUp(); | ||
|
||
TaxEngine::extend(ExampleTaxEngine::ID, ExampleTaxEngine::class); | ||
TaxCalculators::register('example', ExampleTaxCalculator::class); | ||
} | ||
|
||
/** @test */ | ||
public function no_tax_adjustment_gets_created_if_there_is_no_tax_engine_configured() | ||
{ | ||
$product = factory(Product::class)->create(); | ||
config(['vanilo.taxes.engine.driver' => null]); | ||
|
||
Cart::addItem($product); | ||
Checkout::setCart(Cart::getFacadeRoot()); | ||
|
||
$this->assertCount(0, Cart::adjustments()->byType(AdjustmentType::TAX())); | ||
$this->assertEquals(Cart::itemsTotal(), Cart::total()); | ||
} | ||
|
||
/** @test */ | ||
public function no_tax_adjustment_gets_created_if_the_null_driver_is_set() | ||
{ | ||
$product = factory(Product::class)->create(); | ||
config(['vanilo.taxes.engine.driver' => TaxEngineManager::NULL_DRIVER]); | ||
|
||
Cart::addItem($product); | ||
Checkout::setCart(Cart::getFacadeRoot()); | ||
|
||
$this->assertCount(0, Cart::adjustments()->byType(AdjustmentType::TAX())); | ||
$this->assertEquals(Cart::itemsTotal(), Cart::total()); | ||
} | ||
|
||
/** @test */ | ||
public function it_creates_a_tax_adjustment_when_setting_a_tax_engine() | ||
{ | ||
$taxCategory = TaxCategory::create([ | ||
'name' => 'Physical products', | ||
'type' => TaxCategoryType::PHYSICAL_GOODS, | ||
'calculator' => 'example' | ||
]); | ||
$product = factory(Product::class)->create(['price' => 100, 'tax_category_id' => $taxCategory->id]); | ||
config(['vanilo.taxes.engine.driver' => ExampleTaxEngine::ID]); | ||
|
||
Cart::addItem($product); | ||
Checkout::setCart(Cart::getFacadeRoot()); | ||
|
||
/** @var AdjustmentCollection $taxAdjustments */ | ||
$taxAdjustments = Cart::adjustments()->byType(AdjustmentType::TAX()); | ||
$this->assertCount(1, $taxAdjustments); | ||
$taxAdjustment = $taxAdjustments->first(); | ||
$this->assertEquals(19, $taxAdjustment->getAmount()); | ||
$this->assertTrue($taxAdjustment->isCharge()); | ||
$this->assertFalse($taxAdjustment->isIncluded()); | ||
$this->assertEquals(100, Cart::itemsTotal()); | ||
$this->assertEquals(100 + 19, Cart::total()); | ||
} | ||
} |