Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions Model/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,15 @@ public function lookupTaxes($itemsByType, $shippingAssignment, $quote)
}

if (isset($itemsByType[self::ITEM_TYPE_SHIPPING])) {
$addressShippingAmount = (float) $address->getShippingAmount();
foreach ($itemsByType[self::ITEM_TYPE_SHIPPING] as $code => $itemTaxDetail) {
// Shipping as a cart item - shipping needs to be taxed
$shippingRowTotal = $itemTaxDetail[self::KEY_ITEM]->getRowTotal();
$cartItems[] = array(
'ItemID' => 'shipping',
'Index' => $index++,
'TIC' => $this->productTicService->getShippingTic(),
'Price' => $itemTaxDetail[self::KEY_ITEM]->getRowTotal(),
'Price' => ($shippingRowTotal ?: $addressShippingAmount),
'Qty' => 1,
);
}
Expand Down Expand Up @@ -429,7 +431,22 @@ public function lookupTaxes($itemsByType, $shippingAssignment, $quote)
),
);

// hash, check cache
// Call before event (observers may modify $params, e.g. address verification)
$lookupParamsHolder = $this->objectFactory->create();
$lookupParamsHolder->setParams($params);

$this->eventManager->dispatch('taxcloud_lookup_before', array(
'obj' => $lookupParamsHolder,
'customer' => $customer,
'address' => $address,
'quote' => $quote,
'itemsByType' => $itemsByType,
'shippingAssignment' => $shippingAssignment,
));

$params = $lookupParamsHolder->getParams();

// hash, check cache (use post-observer params so cache key matches what we send to TaxCloud)
$cacheKeyApi = 'taxcloud_rates_' . hash('sha256', json_encode($params));
$cacheResult = null;
if ($this->cacheType->load($cacheKeyApi)) {
Expand All @@ -448,21 +465,6 @@ public function lookupTaxes($itemsByType, $shippingAssignment, $quote)
return $result;
}

// Call before event
$obj = $this->objectFactory->create();
$obj->setParams($params);

$this->eventManager->dispatch('taxcloud_lookup_before', array(
'obj' => $obj,
'customer' => $customer,
'address' => $address,
'quote' => $quote,
'itemsByType' => $itemsByType,
'shippingAssignment' => $shippingAssignment,
));

$params = $obj->getParams();

// Call the TaxCloud web service

$this->tclogger->info('Calling lookupTaxes LIVE API');
Expand Down
2 changes: 2 additions & 0 deletions Observer/Sales/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ public function execute(
$result = $this->tcapi->verifyAddress($params['destination']);

if ($result) {
$result['Address1'] = $result['Address1'] ?: ($params['destination']['Address1'] ?? '');
$result['Address2'] = $result['Address2'] ?: ($params['destination']['Address2'] ?? '');
$params['destination'] = $result;
$obj->setParams($params);
}
Expand Down
10 changes: 9 additions & 1 deletion Test/Unit/Mocks/MagentoMocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class Event
{
public function getName() { return ''; }
public function getOrder() { return null; }
public function getObj() { return null; }
}
}

Expand Down Expand Up @@ -335,6 +336,8 @@ public function debug($message) { /* do nothing */ }
namespace Magento\Quote\Model {
class Quote
{
public function getId() { return null; }
public function getCustomer() { return null; }
public function getCustomerTaxClassId() { return null; }
public function getStoreId() { return 1; }
}
Expand Down Expand Up @@ -446,11 +449,16 @@ public function create();
}

namespace Magento\Customer\Api\Data {
interface CustomerInterface
{
public function getId();
}

interface AddressInterfaceFactory
{
public function create();
}

interface RegionInterfaceFactory
{
public function create();
Expand Down
208 changes: 208 additions & 0 deletions Test/Unit/Model/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -628,4 +628,212 @@ public function testGetOrderDetailsReturnsNullWhenNotOkOrError()

$this->assertNull($result, 'getOrderDetails should return null when ResponseType is not OK');
}

/**
* lookupTaxes: when shipping row total is 0, uses address getShippingAmount() for shipping price sent to TaxCloud.
*/
public function testLookupTaxesUsesAddressShippingAmountWhenShippingRowTotalIsZero()
{
$this->scopeConfig->method('getValue')
->willReturnMap([
['tax/taxcloud_settings/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
['tax/taxcloud_settings/logging', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '0'],
['tax/taxcloud_settings/api_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_id'],
['tax/taxcloud_settings/api_key', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_key'],
['tax/taxcloud_settings/cache_lifetime', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '0'],
['tax/taxcloud_settings/guest_customer_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '-1'],
['shipping/origin/postcode', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '60005'],
['shipping/origin/street_line1', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '71 W Seegers Rd'],
['shipping/origin/street_line2', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, ''],
['shipping/origin/city', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'Arlington Heights'],
['shipping/origin/region_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
]);

$region = $this->createMock(\Magento\Directory\Model\Region::class);
$region->method('load')->willReturnSelf();
$region->method('getCode')->willReturn('GA');
$this->regionFactory->method('create')->willReturn($region);

$customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class);
$customer->method('getId')->willReturn(1);

$quote = $this->createMock(\Magento\Quote\Model\Quote::class);
$quote->method('getCustomer')->willReturn($customer);

$address = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
$address->method('getPostcode')->willReturn('30097');
$address->method('getStreet')->willReturn(['405 Victorian Ln']);
$address->method('getCity')->willReturn('Duluth');
$address->method('getRegionId')->willReturn(1);
$address->method('getCountryId')->willReturn('US');
$address->method('getShippingAmount')->willReturn(13.85);

$shipping = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
$shipping->method('getAddress')->willReturn($address);

$shippingAssignment = $this->createMock(\Magento\Quote\Api\Data\ShippingAssignmentInterface::class);
$shippingAssignment->method('getShipping')->willReturn($shipping);
$shippingAssignment->method('getItems')->willReturn([]);

$shippingTaxDetailItem = $this->createMock(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class);
$shippingTaxDetailItem->method('getRowTotal')->willReturn(0);

$itemsByType = [
Api::ITEM_TYPE_SHIPPING => [
'shipping' => [Api::KEY_ITEM => $shippingTaxDetailItem],
],
];

$this->productTicService->method('getShippingTic')->willReturn('11010');

$this->cacheType->method('load')->willReturn(false);

$capturedParams = null;
$this->mockDataObject->method('setParams')->willReturnCallback(function ($p) use (&$capturedParams) {
$capturedParams = $p;
return $this->mockDataObject;
});
$this->mockDataObject->method('getParams')->willReturnCallback(function () use (&$capturedParams) {
return $capturedParams;
});
$this->mockDataObject->method('setResult')->willReturnSelf();
$this->mockDataObject->method('getResult')->willReturn([
'ResponseType' => 'OK',
'CartItemsResponse' => ['CartItemResponse' => [['CartItemIndex' => 0, 'TaxAmount' => 0]]],
]);
$this->objectFactory->method('create')->willReturn($this->mockDataObject);

$lookupParams = null;
$mockLookupResponse = new \stdClass();
$mockLookupResponse->LookupResult = new \stdClass();
$mockLookupResponse->LookupResult->ResponseType = 'OK';
$mockLookupResponse->LookupResult->CartItemsResponse = new \stdClass();
$mockLookupResponse->LookupResult->CartItemsResponse->CartItemResponse = [
(object)['CartItemIndex' => 0, 'TaxAmount' => 0],
];
$this->mockSoapClient->method('lookup')->willReturnCallback(function ($params) use (&$lookupParams, $mockLookupResponse) {
$lookupParams = $params;
return $mockLookupResponse;
});

$this->api->lookupTaxes($itemsByType, $shippingAssignment, $quote);

$this->assertNotNull($lookupParams, 'lookup should have been called');
$cartItems = $lookupParams['cartItems'] ?? [];
$shippingItem = null;
foreach ($cartItems as $item) {
if (isset($item['ItemID']) && $item['ItemID'] === 'shipping') {
$shippingItem = $item;
break;
}
}
$this->assertNotNull($shippingItem, 'cartItems should contain shipping');
$this->assertSame(13.85, (float) $shippingItem['Price'], 'lookupTaxes should send address getShippingAmount() when shipping row total is 0');
}

/**
* lookupTaxes: cache key is computed from params after taxcloud_lookup_before event.
*/
public function testLookupTaxesCacheKeyUsesParamsAfterEvent()
{
$this->scopeConfig->method('getValue')
->willReturnMap([
['tax/taxcloud_settings/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
['tax/taxcloud_settings/logging', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '0'],
['tax/taxcloud_settings/api_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_id'],
['tax/taxcloud_settings/api_key', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'test_api_key'],
['tax/taxcloud_settings/cache_lifetime', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '3600'],
['tax/taxcloud_settings/guest_customer_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '-1'],
['shipping/origin/postcode', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '60005'],
['shipping/origin/street_line1', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '71 W Seegers Rd'],
['shipping/origin/street_line2', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, ''],
['shipping/origin/city', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, 'Arlington Heights'],
['shipping/origin/region_id', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, null, '1'],
]);

$region = $this->createMock(\Magento\Directory\Model\Region::class);
$region->method('load')->willReturnSelf();
$region->method('getCode')->willReturn('GA');
$this->regionFactory->method('create')->willReturn($region);

$customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class);
$customer->method('getId')->willReturn(1);
$quote = $this->createMock(\Magento\Quote\Model\Quote::class);
$quote->method('getCustomer')->willReturn($customer);

$address = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
$address->method('getPostcode')->willReturn('30097');
$address->method('getStreet')->willReturn(['405 Victorian Ln']);
$address->method('getCity')->willReturn('Duluth');
$address->method('getRegionId')->willReturn(1);
$address->method('getCountryId')->willReturn('US');
$address->method('getShippingAmount')->willReturn(0);

$shipping = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
$shipping->method('getAddress')->willReturn($address);
$shippingAssignment = $this->createMock(\Magento\Quote\Api\Data\ShippingAssignmentInterface::class);
$shippingAssignment->method('getShipping')->willReturn($shipping);
$shippingAssignment->method('getItems')->willReturn([]);

$shippingTaxDetailItem = $this->createMock(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class);
$shippingTaxDetailItem->method('getRowTotal')->willReturn(0);
$itemsByType = [
Api::ITEM_TYPE_SHIPPING => [
'shipping' => [Api::KEY_ITEM => $shippingTaxDetailItem],
],
];

$this->productTicService->method('getShippingTic')->willReturn('11010');
$this->cacheType->method('load')->willReturn(false);

$modifiedDestination = [
'Address1' => 'Modified Street By Observer',
'Address2' => '',
'City' => 'Duluth',
'State' => 'GA',
'Zip5' => '30097',
'Zip4' => '',
];
$this->mockDataObject->method('setParams')->willReturnSelf();
$this->mockDataObject->method('getParams')->willReturnCallback(function () use ($modifiedDestination) {
$base = [
'apiLoginID' => 'test_api_id',
'apiKey' => 'test_api_key',
'customerID' => 1,
'cartID' => null,
'cartItems' => [['ItemID' => 'shipping', 'Index' => 0, 'TIC' => '11010', 'Price' => 0, 'Qty' => 1]],
'origin' => ['Address1' => '71 W Seegers Rd', 'City' => 'Arlington Heights', 'State' => 'GA', 'Zip5' => '60005', 'Zip4' => null],
'destination' => $modifiedDestination,
'deliveredBySeller' => false,
'exemptCert' => ['CertificateID' => null],
];
return $base;
});
$this->mockDataObject->method('setResult')->willReturnSelf();
$this->mockDataObject->method('getResult')->willReturn([
'ResponseType' => 'OK',
'CartItemsResponse' => ['CartItemResponse' => [['CartItemIndex' => 0, 'TaxAmount' => 0]]],
]);
$this->objectFactory->method('create')->willReturn($this->mockDataObject);

$cacheKeyUsed = null;
$this->cacheType->method('load')->willReturnCallback(function ($key) use (&$cacheKeyUsed) {
$cacheKeyUsed = $key;
return false;
});

$mockLookupResponse = new \stdClass();
$mockLookupResponse->LookupResult = new \stdClass();
$mockLookupResponse->LookupResult->ResponseType = 'OK';
$mockLookupResponse->LookupResult->CartItemsResponse = new \stdClass();
$mockLookupResponse->LookupResult->CartItemsResponse->CartItemResponse = [
(object)['CartItemIndex' => 0, 'TaxAmount' => 0],
];
$this->mockSoapClient->method('lookup')->willReturn($mockLookupResponse);

$this->api->lookupTaxes($itemsByType, $shippingAssignment, $quote);

$expectedKey = 'taxcloud_rates_' . hash('sha256', json_encode($this->mockDataObject->getParams()));
$this->assertSame($expectedKey, $cacheKeyUsed, 'lookupTaxes cache key should be computed from params after taxcloud_lookup_before event');
}
}
Loading
Loading