With the advent of the B2B channel there is the requirement to display prices depending on customer's type. This way there might be users belonging to a B2B customer as well individual users in one channel. Net prices should be displayed to B2B users, whereas gross prices should be displayed to B2C customers.
Furthermore only one price type - either product's net or gross price - can be imported in Intershop 7's database. This way there will be no functionality to handle net as well as gross prices, both stored in the database, in the same channel at the same time. One price type is dynamically calculated from the other one stored in the database. In fact at runtime the tax that has to be applied to a net price (stored in the database) and the resulting gross price is dynamically calculated. Same way the net price is calculated by dynamically determining and subtracting the included tax from gross price, stored in the database.
For this reason new Taxation Services are implemented. This common interface of the Taxation Service supports tax calculation for different subjects to be taxed beyond a simple product price. Other taxable subjects might be shipping costs, payment costs, promotions, duties, fees or complex subjects, like entire baskets.
Based on the tax calculated using the Taxation Service, one price type can be determined from the other one. Note that it is not in the responsibility of the Taxation Service to calculate the price of a specific price type from the other one. Taxation Service only provides the functionality to calculate the tax that has to applied to or included in a given price. This way calculation net from gross price based on the included tax, provided by the Taxation Service, is in the responsibility of the calling business logic.
One precondition for calculating one price type from the other is a clear indicator, what prices are stored in the database at all. For this reason the new preference PricingScheme
is introduced, to resolve the problem, that price type imported in database can only be determined by calculation relevant preference TaxationPolicy
.
Preference (PriceType: net|gross
) indicates, whether imported prices have to be interpreted as net or gross prices.
Preference (PriceDisplay: net|gross
) indicates, whether prices have to be presented to a specific customer as net or gross prices. Furthermore this preference influences the basket calculation.
Note
The value of this preference is not an indicator, whether net or gross prices have been imported in the database any longer. This way a long-term Enfinity 2.x/EnfinitySuite 6.x/Intershop 7 semantically issue is resolved now, that results in a lot of confusion over the years.
Preference (DefaultPriceDisplay: Individual Customer|Business Customer
) select which price display is used for logged-out customers.
TaxationService
Note
The basket TaxationService
interface is the base of tangible taxation services for different subjects to be taxed. Intershop 7.4.1.0 introduces a Taxation Service for product prices, other subjects to be taxed might follow in future Intershop 7 versions.
public interface TaxationService<S, R extends TaxRecord> { R getTaxRecord(S subject, Money price, TaxationContext context); }
The method TaxationService::getTaxRecord(S subject, Money price, TaxationContext context)
returns tax related information for the given price and the specified subject to be taxed. A tangible Taxation Service has to provide return values for subject
and TaxRecord
based on the subjects to be taxed, like product or basket related taxation services.
The result type TaxRecord
only contains two methods:
public interface TaxRecord { Boolean isFailure(); String getFailureCode(); }
If a tax calculation/retrieval finished with an error. All other tax relevant data have to be specified in derivations of TaxRecord
, because the tax information might have a simple structure, e.g., in the case of a simple product price, or a complex one in case of an entire basket.
TaxationContext
The TaxationContext
interface can be used to pass additional information to the TaxationService
that might be required for tax calculation, like the tax exempt flag.
Addresses passed in the TaxationContext
can be used by the implementation for determining the correct tax jurisdiction.
public interface TaxationContext { Boolean isTaxExempt(); AddressBO getInvoiceToAddress(); AddressBO getShipToAddress(); AddressBO getShipFromAddress(); }
OrderTaxationService
should not be derived from TaxationService
.api_service
.TaxableOrder
and OrderTaxRecord
should be build with the interfaces and classes defined in com.intershop.api.data.*
packages with Payable
as blueprint. The main difference between both data structures is, that OrderTaxRecord
allows the Managed Service to add tax items.TaxationContext
should be only used if necessary, e.g. for submitting Service Configuration, but not for data/information that are related to the subject of taxation, e.g. address or tax exempt flag.ProductTaxationService
The ProductTaxationService
is a specific type of the common TaxationService
, used to determine taxes applied to/included in single product prices.
public interface TaxationService<ProductTaxationSubject, SingleItemTaxRecord> { SingleItemTaxRecord getTaxRecord(ProductTaxationSubject subject, Money price, TaxationContext context); }
The method ProductTaxationService::getTaxRecord(S subject, Money price, TaxationContext context)
returns tax related information for the given product and the specified price.
Note
It is up to the implementation if the TaxationService
interface to interpret the specified price as net or gross price.
This way there are different implementations of this interface for the two scenarios:
Scenario 1: Specified price is interpreted as net price
The implementation of this interface interprets the specified price as net price. In this case the tax amount will be returned that is applied at the given price. This amount is calculated based on the given net price and the tax rate, that is assigned to the specified subject to be taxed.
In detail:
SingleItemTaxRecord::getCalculatedTax()
returns the tax added to the given net price.SingleItemTaxRecord::getTaxableAmount()
returns the amount, the tax is calculated for. (In most cases this amount is equal to the given (net) price.)Scenario 2: Specified price is interpreted as gross price
The implementation of this interface interprets the specified price as gross price. In this case the tax amount will be returned that is included in the given price. This amount is calculated based on the given gross price and the tax rate, that is assigned to the specified subject to be taxed.
In detail:
TaxRecord::getCalculatedTax()
returns the tax included in the given gross price.TaxRecord::getTaxableAmount()
returns the amount, the included tax is calculated from. (In most cases this amount is equal to the net price.)It is in the responsibility of the implementation to determine the tax rate for the given product.
ProductTaxationSubject
In some scenarios the tax cannot be calculated with the information only contained at the product provided to the ProductTaxationService
. One example is a warranty assigned to another product. The tax class of that dependent warranty is determined by the one specified for the product the warranty is assigned too. For this reason the type ProductTaxationSubject
has been defined:
public interface ProductTaxationSubject { ProductBO getProduct(); ProductBO getContextProduct(); }
The method ProductTaxationSubject::getProduct()
provides the product the tax should be calculated for to the service. In case of a dependent warranty the method ProductTaxationSubject::getContextProduct()
also provides the product the warranty is assigned to to the service.
SingleItemTaxRecord
The SingleItemTaxRecord
returned by the ProductTaxationService
contains the tax information calculated/retrieved by the service.
public interface SingleItemTaxRecord extends TaxRecord, TaxItem { /* --- from TaxRecord --- */ Boolean isFailure(); String getFailureCode(); /* --- from SingleItemTaxRecord --- */ Boolean hasMultipleTaxes(); Collection<? extends TaxItem> getTaxItems(); /* --- from TaxItem --- */ String getSitus(); Jurisdiction getJurisdiction(); Money getCalculatedTax(); BigDecimal getEffectiveTaxRate(); Money getTaxableAmount(); Money getNonTaxableAmount(); }
In net price based scenarios there might be (depending on the region and the taxation scheme) between zero and up to nine taxes applied to a single product (price), whereas in VAT based scenarios there is only one tax to product price. To cover all scenarios, the SingleItemTaxRecord
may contain multiple taxes.
VAT based scenarios (EU)
SingleItemTaxRecord::hasMultipleTaxes()
returns Boolean::FALSE
SingleItemTaxRecord::getTaxItems()
- An empty Collection
.SingleItemTaxRecord::getSitus()
- The situs/place of taxation.SingleItemTaxRecord::getJurisdiction()
- The jurisdiction, an area subject to its own distinct tax regulations.SingleItemTaxRecord::getEffectiveTaxRate()
- The valid VAT tax rate for the product.SingleItemTaxRecord::getCalculatedTax()
- The calculated VAT.SingleItemTaxRecord::getTaxableAmount()
- The net price.SingleItemTaxRecord::getNonTaxableAmount() - com.intershop.beehive.foundation.quantity.Money::NOT_AVAILABLE
Non-VAT based scenarios (North America/Canada)
SingleItemTaxRecord::hasMultipleTaxes()
returns Boolean::TRUE
SingleItemTaxRecord::getTaxItems()
- A Collection
containing all tax items.SingleItemTaxRecord::getSitus()
-SingleItemTaxRecord::getJurisdiction()
-SingleItemTaxRecord::getEffectiveTaxRate()
-SingleItemTaxRecord::getCalculatedTax()
- The sum of calculated tax amounts of all tax items returned by SingleItemTaxRecord::getTaxItems()
.SingleItemTaxRecord::getTaxableAmount()
- The net price.SingleItemTaxRecord::getNonTaxableAmount() - com.intershop.beehive.foundation.quantity.Money::NOT_AVAILABLE
In contrast to previous Enfinity Suite/Intershop 7 versions the new approach clearly separates different functionalities by responsibilities. The Taxation Service is responsible for tax calculation. An extension of ProductBO
type is responsible to determine product price either from the product data or a price lists. The product's net price can be calculated from gross price and vice versa, by adding/subtracting the tax, retrieved by calling the Taxation Service, to/from net/gross price.
Furthermore the basket calculation has not to calculate product price related taxes any longer, but retrieves the net or gross price directly from the ProductBO
extension.
Note
In some scenarios the tax, even for product prices, is calculated at the end of the checkout process for the first time (North America/Canada).
The interpretation of prices and costs as net or gross values is controlled by the PriceType
configuration value. This way the decision, which calculation rule set has to be used for calculating the basket of a certain customer is met based on the specific value set for PriceType
:
PriceType=net
-> NetBasedCalculation_V2PriceType=gross
-> GrossBasedCalculation_V2The ID of the chosen calculation rule set is stored at the line item container (basket/order) as part of the computed items.
Following prices and costs follow the value specified for PriceType:
The calculation results displayed to the customer are controlled by the value of PriceDisplayType
. The PriceDisplayType
is part of the object PriceDisplayPreferences
.
The PriceDisplayPreferences
can be retrieved from the UserBO. Because it may not be available anymore later, a copy of the value is stored at the basket on its creation and updated on user change (login).
When placing the order the value is transferred and stored.