Skip to content

Commit a2dce80

Browse files
authored
Allow shipping tax rate to be determined by a callable (lunarphp#2423)
1 parent 271c964 commit a2dce80

3 files changed

Lines changed: 125 additions & 2 deletions

File tree

packages/table-rate-shipping/config/shipping-tables.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
/*
77
* What method should we use for a shipping rate tax calculation?
88
* Options are 'default' for the system-wide default tax rate,
9-
* or 'highest' to select the highest tax rate in the cart
9+
* 'highest' to select the highest tax rate in the cart
10+
* or add a callable to use your own logic (eg => [MyTaxRateCalculator::class, 'calculate'])
1011
*/
1112
'shipping_rate_tax_calculation' => 'default',
1213
];

packages/table-rate-shipping/src/Models/ShippingRate.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ public function getThumbnail(): ?string
144144
*/
145145
public function getShippingOption(CartContract $cart): ?ShippingOption
146146
{
147-
if (config('lunar.shipping-tables.shipping_rate_tax_calculation') == 'highest') {
147+
$calculateBy = config('lunar.shipping-tables.shipping_rate_tax_calculation');
148+
149+
if (is_callable($calculateBy)) {
150+
$this->resolvedTaxClass = call_user_func($calculateBy, $cart);
151+
} elseif ($calculateBy == 'highest') {
148152
$this->resolvedTaxClass = $this->resolveHighestTaxRateInCart($cart);
149153
}
150154

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
uses(\Lunar\Tests\Shipping\TestCase::class);
4+
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
5+
uses(\Lunar\Tests\Shipping\TestUtils::class);
6+
7+
use Illuminate\Support\Facades\Config;
8+
use Lunar\Models\Cart;
9+
use Lunar\Models\Currency;
10+
use Lunar\Models\TaxClass;
11+
use Lunar\Shipping\Models\ShippingMethod;
12+
use Lunar\Shipping\Models\ShippingRate;
13+
use Lunar\Shipping\Models\ShippingZone;
14+
15+
function makeShippingRate(Currency $currency): ShippingRate
16+
{
17+
$shippingZone = ShippingZone::factory()->create(['type' => 'countries']);
18+
19+
$shippingMethod = ShippingMethod::factory()->create([
20+
'driver' => 'flat-rate',
21+
'data' => ['charge' => ["{$currency->code}" => 500]],
22+
]);
23+
24+
$customerGroup = \Lunar\Models\CustomerGroup::factory()->create(['default' => true]);
25+
$shippingMethod->customerGroups()->sync([
26+
$customerGroup->id => ['enabled' => true, 'visible' => true, 'starts_at' => now(), 'ends_at' => null],
27+
]);
28+
29+
$shippingRate = ShippingRate::factory()->create([
30+
'shipping_method_id' => $shippingMethod->id,
31+
'shipping_zone_id' => $shippingZone->id,
32+
]);
33+
34+
$shippingRate->prices()->create([
35+
'price' => 500,
36+
'min_quantity' => 1,
37+
'currency_id' => $currency->id,
38+
]);
39+
40+
return $shippingRate;
41+
}
42+
43+
test('getTaxClass returns the default tax class when config is default', function () {
44+
$currency = Currency::factory()->create(['default' => true]);
45+
$defaultTaxClass = TaxClass::factory()->create(['default' => true]);
46+
47+
Config::set('lunar.shipping-tables.shipping_rate_tax_calculation', 'default');
48+
49+
$shippingRate = makeShippingRate($currency);
50+
$cart = $this->createCart($currency, calculate: false);
51+
52+
$shippingRate->getShippingOption($cart);
53+
54+
expect($shippingRate->getTaxClass()->id)->toBe($defaultTaxClass->id);
55+
})->group('shipping-rate');
56+
57+
test('getTaxClass uses callable config to resolve tax class', function () {
58+
$currency = Currency::factory()->create(['default' => true]);
59+
TaxClass::factory()->create(['default' => true]);
60+
$customTaxClass = TaxClass::factory()->create(['name' => 'Custom Tax Class']);
61+
62+
Config::set('lunar.shipping-tables.shipping_rate_tax_calculation', function (Cart $cart) use ($customTaxClass) {
63+
return $customTaxClass;
64+
});
65+
66+
$shippingRate = makeShippingRate($currency);
67+
$cart = $this->createCart($currency, calculate: false);
68+
69+
$shippingRate->getShippingOption($cart);
70+
71+
expect($shippingRate->getTaxClass()->id)->toBe($customTaxClass->id);
72+
})->group('shipping-rate');
73+
74+
test('callable config receives the cart instance', function () {
75+
$currency = Currency::factory()->create(['default' => true]);
76+
$defaultTaxClass = TaxClass::factory()->create(['default' => true]);
77+
78+
$receivedCart = null;
79+
80+
Config::set('lunar.shipping-tables.shipping_rate_tax_calculation', function (Cart $cart) use (&$receivedCart, $defaultTaxClass) {
81+
$receivedCart = $cart;
82+
83+
return $defaultTaxClass;
84+
});
85+
86+
$shippingRate = makeShippingRate($currency);
87+
$cart = $this->createCart($currency, calculate: false);
88+
89+
$shippingRate->getShippingOption($cart);
90+
91+
expect($receivedCart)->not->toBeNull()
92+
->and($receivedCart->id)->toBe($cart->id);
93+
})->group('shipping-rate');
94+
95+
test('getTaxClass uses an invokable class as callable config', function () {
96+
$currency = Currency::factory()->create(['default' => true]);
97+
TaxClass::factory()->create(['default' => true]);
98+
$customTaxClass = TaxClass::factory()->create(['name' => 'Invokable Tax Class']);
99+
100+
$resolver = new class($customTaxClass)
101+
{
102+
public function __construct(private TaxClass $taxClass) {}
103+
104+
public function __invoke(Cart $cart): TaxClass
105+
{
106+
return $this->taxClass;
107+
}
108+
};
109+
110+
Config::set('lunar.shipping-tables.shipping_rate_tax_calculation', $resolver);
111+
112+
$shippingRate = makeShippingRate($currency);
113+
$cart = $this->createCart($currency, calculate: false);
114+
115+
$shippingRate->getShippingOption($cart);
116+
117+
expect($shippingRate->getTaxClass()->id)->toBe($customTaxClass->id);
118+
})->group('shipping-rate');

0 commit comments

Comments
 (0)