diff --git a/src/Business/Grand.Business.Catalog/Services/Prices/PriceFormatter.cs b/src/Business/Grand.Business.Catalog/Services/Prices/PriceFormatter.cs
index 0bd8c531b9..aa8e381421 100644
--- a/src/Business/Grand.Business.Catalog/Services/Prices/PriceFormatter.cs
+++ b/src/Business/Grand.Business.Catalog/Services/Prices/PriceFormatter.cs
@@ -1,6 +1,5 @@
using Grand.Business.Core.Interfaces.Catalog.Prices;
using Grand.Domain.Directory;
-using Grand.Infrastructure;
using System.Globalization;
namespace Grand.Business.Catalog.Services.Prices;
@@ -10,21 +9,6 @@ namespace Grand.Business.Catalog.Services.Prices;
///
public class PriceFormatter : IPriceFormatter
{
- #region Fields
-
- private readonly IContextAccessor _contextAccessor;
-
- #endregion
-
- #region Constructors
-
- public PriceFormatter(IContextAccessor contextAccessor)
- {
- _contextAccessor = contextAccessor;
- }
-
- #endregion
-
#region Utilities
///
@@ -66,16 +50,6 @@ protected virtual string GetCurrencyString(double amount, Currency targetCurrenc
#region Methods
- ///
- /// Formats the price
- ///
- /// Price
- /// Price
- public virtual string FormatPrice(double price)
- {
- return FormatPrice(price, _contextAccessor.WorkContext.WorkingCurrency);
- }
-
///
/// Formats the price
///
diff --git a/src/Business/Grand.Business.Catalog/Services/Prices/PricingService.cs b/src/Business/Grand.Business.Catalog/Services/Prices/PricingService.cs
index 6a538ec2b5..c49feb9910 100644
--- a/src/Business/Grand.Business.Catalog/Services/Prices/PricingService.cs
+++ b/src/Business/Grand.Business.Catalog/Services/Prices/PricingService.cs
@@ -11,7 +11,6 @@
using Grand.Domain.Directory;
using Grand.Domain.Orders;
using Grand.Domain.Stores;
-using Grand.Infrastructure;
using Grand.Mediator;
using System.Globalization;
@@ -25,7 +24,6 @@ public class PricingService : IPricingService
#region Ctor
public PricingService(
- IContextAccessor contextAccessor,
IProductService productService,
IMediator mediator,
ICurrencyService currencyService,
@@ -33,7 +31,6 @@ public PricingService(
ShoppingCartSettings shoppingCartSettings,
CatalogSettings catalogSettings)
{
- _contextAccessor = contextAccessor;
_productService = productService;
_mediator = mediator;
_currencyService = currencyService;
@@ -58,7 +55,6 @@ protected class ProductPrice
#region Fields
- private readonly IContextAccessor _contextAccessor;
private readonly IProductService _productService;
private readonly IMediator _mediator;
private readonly ICurrencyService _currencyService;
@@ -210,18 +206,21 @@ await _currencyService.ConvertToPrimaryStoreCurrency(price, currency))
///
/// The shopping cart item
/// Product
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// Include discounts or not for price
/// Shopping cart unit price (one item)
public virtual async Task<(double unitprice, double discountAmount, List appliedDiscounts)>
- GetUnitPrice(ShoppingCartItem shoppingCartItem,
- Product product, bool includeDiscounts = true)
+ GetUnitPrice(ShoppingCartItem shoppingCartItem, Product product, Customer customer, Store store,
+ Currency currency, bool includeDiscounts = true)
{
ArgumentNullException.ThrowIfNull(shoppingCartItem);
return await GetUnitPrice(product,
- _contextAccessor.WorkContext.CurrentCustomer,
- _contextAccessor.StoreContext.CurrentStore,
- _contextAccessor.WorkContext.WorkingCurrency,
+ customer,
+ store,
+ currency,
shoppingCartItem.ShoppingCartTypeId,
shoppingCartItem.Quantity,
shoppingCartItem.Attributes,
@@ -303,7 +302,8 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(combination.OverriddenPri
var attributeValues = product.ParseProductAttributeValues(attributes);
if (attributeValues != null)
foreach (var attributeValue in attributeValues)
- attributesTotalPrice += await GetProductAttributeValuePriceAdjustment(attributeValue);
+ attributesTotalPrice +=
+ await GetProductAttributeValuePriceAdjustment(attributeValue, customer, store, currency);
if (product.ProductTypeId == ProductType.BundledProduct)
foreach (var item in product.BundleProducts)
{
@@ -312,7 +312,8 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(combination.OverriddenPri
if (bundledProductsAttributeValues == null) continue;
foreach (var attributeValue in bundledProductsAttributeValues)
attributesTotalPrice += item.Quantity *
- await GetProductAttributeValuePriceAdjustment(attributeValue);
+ await GetProductAttributeValuePriceAdjustment(attributeValue,
+ customer, store, currency);
}
}
@@ -355,17 +356,20 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(combination.OverriddenPri
///
/// The shopping cart item
/// Product
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// Include discounts or not for price
/// Shopping cart item sub total
public virtual async Task<(double subTotal, double discountAmount, List appliedDiscounts)>
- GetSubTotal(ShoppingCartItem shoppingCartItem, Product product,
- bool includeDiscounts = true)
+ GetSubTotal(ShoppingCartItem shoppingCartItem, Product product, Customer customer, Store store,
+ Currency currency, bool includeDiscounts = true)
{
ArgumentNullException.ThrowIfNull(shoppingCartItem);
double subTotal = 0;
//unit price
- var getUnitPrice = await GetUnitPrice(shoppingCartItem, product, includeDiscounts);
+ var getUnitPrice = await GetUnitPrice(shoppingCartItem, product, customer, store, currency, includeDiscounts);
var unitPrice = getUnitPrice.unitprice;
var discountAmount = getUnitPrice.discountAmount;
var appliedDiscounts = getUnitPrice.appliedDiscounts;
@@ -385,7 +389,8 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(combination.OverriddenPri
discountAmount *= discountedQuantity;
var notDiscountedQuantity = shoppingCartItem.Quantity - discountedQuantity;
- var notDiscountedUnitPrice = await GetUnitPrice(shoppingCartItem, product, false);
+ var notDiscountedUnitPrice =
+ await GetUnitPrice(shoppingCartItem, product, customer, store, currency, false);
var notDiscountedSubTotal = notDiscountedUnitPrice.unitprice * notDiscountedQuantity;
subTotal = discountedSubTotal + notDiscountedSubTotal;
@@ -445,8 +450,12 @@ public virtual async Task GetProductCost(Product product, IList
/// Product attribute value
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// Price adjustment
- public virtual async Task GetProductAttributeValuePriceAdjustment(ProductAttributeValue value)
+ public virtual async Task GetProductAttributeValuePriceAdjustment(ProductAttributeValue value,
+ Customer customer, Store store, Currency currency)
{
ArgumentNullException.ThrowIfNull(value);
@@ -459,8 +468,7 @@ public virtual async Task GetProductAttributeValuePriceAdjustment(Produc
adjustment = value.PriceAdjustment;
if (adjustment > 0)
adjustment =
- await _currencyService.ConvertFromPrimaryStoreCurrency(adjustment,
- _contextAccessor.WorkContext.WorkingCurrency);
+ await _currencyService.ConvertFromPrimaryStoreCurrency(adjustment, currency);
}
break;
case AttributeValueType.AssociatedToProduct:
@@ -469,9 +477,9 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(adjustment,
var associatedProduct = await _productService.GetProductById(value.AssociatedProductId);
if (associatedProduct != null)
adjustment = (await GetFinalPrice(associatedProduct,
- _contextAccessor.WorkContext.CurrentCustomer,
- _contextAccessor.StoreContext.CurrentStore,
- _contextAccessor.WorkContext.WorkingCurrency,
+ customer,
+ store,
+ currency,
value.PriceAdjustment)).finalPrice * value.Quantity;
}
break;
diff --git a/src/Business/Grand.Business.Catalog/Services/Products/ProductAttributeFormatter.cs b/src/Business/Grand.Business.Catalog/Services/Products/ProductAttributeFormatter.cs
index 7360dbd0dd..3aa63362ab 100644
--- a/src/Business/Grand.Business.Catalog/Services/Products/ProductAttributeFormatter.cs
+++ b/src/Business/Grand.Business.Catalog/Services/Products/ProductAttributeFormatter.cs
@@ -6,6 +6,7 @@
using Grand.Domain.Common;
using Grand.Domain.Customers;
using Grand.Domain.Orders;
+using Grand.Domain.Tax;
using Grand.Infrastructure;
using Grand.SharedKernel.Extensions;
using System.Net;
@@ -85,8 +86,8 @@ public virtual async Task FormatAttributes(Product product, IList FormatAttributes(Product product, IList x.Name, langId)) : p1.GetTranslation(x => x.Name, langId))}");
- var formattedAttribute = await PrepareFormattedAttribute(p1, customAttributes, langId, separator,
- htmlEncode,
+ var formattedAttribute = await PrepareFormattedAttribute(p1, customAttributes, customer, langId,
+ separator, htmlEncode,
renderPrices, allowHyperlinks, showInAdmin);
if (formattedAttribute.Length > 0)
{
@@ -150,10 +151,11 @@ public virtual async Task FormatAttributes(Product product, IList PrepareFormattedAttribute(Product product,
- IList customAttributes, string langId,
+ IList customAttributes, Customer customer, string langId,
string separator, bool htmlEncode, bool renderPrices,
bool allowHyperlinks, bool showInAdmin)
{
+ var priceCustomer = customer ?? _contextAccessor.WorkContext.CurrentCustomer;
var result = new StringBuilder();
var attributes = product.ParseProductAttributeMappings(customAttributes);
for (var i = 0; i < attributes.Count; i++)
@@ -233,9 +235,13 @@ private async Task PrepareFormattedAttribute(Product product,
if (renderPrices)
{
var attributeValuePriceAdjustment =
- await _pricingService.GetProductAttributeValuePriceAdjustment(attributeValue);
+ await _pricingService.GetProductAttributeValuePriceAdjustment(attributeValue,
+ priceCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency);
var (priceAdjustmentBase, _) = await _taxService.GetProductPrice(product,
- attributeValuePriceAdjustment, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore);
+ attributeValuePriceAdjustment,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ priceCustomer, _contextAccessor.StoreContext.CurrentStore);
switch (priceAdjustmentBase)
{
case > 0:
diff --git a/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs b/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs
index e1110c8890..e3ee25ff42 100644
--- a/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs
+++ b/src/Business/Grand.Business.Catalog/Services/Tax/TaxService.cs
@@ -8,7 +8,6 @@
using Grand.Domain.Orders;
using Grand.Domain.Stores;
using Grand.Domain.Tax;
-using Grand.Infrastructure;
using Grand.Infrastructure.Extensions;
using Microsoft.Extensions.Logging;
@@ -22,7 +21,6 @@ public class TaxService : ITaxService
#region Ctor
public TaxService(
- IContextAccessor contextAccessor,
IGroupService groupService,
ICountryService countryService,
IEnumerable taxProviders,
@@ -32,7 +30,6 @@ public TaxService(
CustomerSettings customerSettings,
AddressSettings addressSettings)
{
- _contextAccessor = contextAccessor;
_groupService = groupService;
_taxSettings = taxSettings;
_taxProviderSettings = taxProviderSettings;
@@ -47,7 +44,6 @@ public TaxService(
#region Fields
- private readonly IContextAccessor _contextAccessor;
private readonly IGroupService _groupService;
private readonly ICountryService _countryService;
private readonly IEnumerable _taxProviders;
@@ -193,7 +189,7 @@ protected virtual double CalculatePrice(double price, double percent, bool incre
var isTaxable = true;
//active tax provider
- var activeTaxProvider = LoadActiveTaxProvider();
+ var activeTaxProvider = LoadActiveTaxProvider(customer, store?.Id);
if (activeTaxProvider == null)
return (taxRate, true);
@@ -235,11 +231,13 @@ await IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer))
///
/// Load active tax provider
///
+ /// Customer whose groups limit the fallback providers
+ /// Store whose providers are the fallback
/// Active tax provider
- public virtual ITaxProvider LoadActiveTaxProvider()
+ public virtual ITaxProvider LoadActiveTaxProvider(Customer customer, string storeId)
{
var taxProvider = LoadTaxProviderBySystemName(_taxProviderSettings.ActiveTaxProviderSystemName) ??
- LoadAllTaxProviders(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id)
+ LoadAllTaxProviders(customer, storeId)
.FirstOrDefault();
return taxProvider;
}
@@ -270,31 +268,6 @@ public virtual IList LoadAllTaxProviders(Customer customer = null,
}
- ///
- /// Gets price
- ///
- /// Product
- /// Price
- /// Price
- public virtual async Task<(double productprice, double taxRate)> GetProductPrice(Product product, double price)
- {
- return await GetProductPrice(product, price, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore);
- }
-
- ///
- /// Gets price
- ///
- /// Product
- /// Price
- /// Customer
- /// Store
- /// Price
- public virtual async Task<(double productprice, double taxRate)> GetProductPrice(Product product, double price, Customer customer, Store store)
- {
- var includingTax = _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
- return await GetProductPrice(product, price, includingTax, customer, store);
- }
-
///
/// Gets price
///
@@ -461,18 +434,6 @@ bool priceIncludesTax
return productPrice;
}
- ///
- /// Gets shipping price
- ///
- /// Price
- /// Customer
- /// Price
- public virtual async Task<(double shippingPrice, double taxRate)> GetShippingPrice(double price, Customer customer, Store store)
- {
- var includingTax = _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
- return await GetShippingPrice(price, includingTax, customer, store);
- }
-
///
/// Gets shipping price
///
@@ -492,19 +453,6 @@ bool priceIncludesTax
return (prices.productprice, prices.taxRate);
}
- ///
- /// Gets payment method additional handling fee
- ///
- /// Price
- /// Customer
- /// Store
- /// Price
- public virtual async Task<(double paymentPrice, double taxRate)> GetPaymentMethodAdditionalFee(double price, Customer customer, Store store)
- {
- var includingTax = _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
- return await GetPaymentMethodAdditionalFee(price, includingTax, customer, store);
- }
-
///
/// Gets payment method additional handling fee
///
@@ -522,33 +470,6 @@ bool priceIncludesTax
return (prices.productprice, prices.taxRate);
}
- ///
- /// Gets checkout attribute value price
- ///
- /// Checkout attribute
- /// Checkout attribute value
- /// Price
- public virtual async Task<(double checkoutPrice, double taxRate)> GetCheckoutAttributePrice(CheckoutAttribute ca, CheckoutAttributeValue cav)
- {
- var customer = _contextAccessor.WorkContext.CurrentCustomer;
- var store = _contextAccessor.StoreContext.CurrentStore;
- return await GetCheckoutAttributePrice(ca, cav, customer, store);
- }
-
- ///
- /// Gets checkout attribute value price
- ///
- /// Checkout attribute
- /// Checkout attribute value
- /// Customer
- /// Store
- /// Price
- public virtual async Task<(double checkoutPrice, double taxRate)> GetCheckoutAttributePrice(CheckoutAttribute ca, CheckoutAttributeValue cav, Customer customer, Store store)
- {
- var includingTax = _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
- return await GetCheckoutAttributePrice(ca, cav, includingTax, customer, store);
- }
-
///
/// Gets checkout attribute value price
///
diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs
index 60803f6efc..15584bdef1 100644
--- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs
+++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/PlaceOrderCommandHandler.cs
@@ -377,6 +377,11 @@ private async Task GetShoppingCartItemWeight(ShoppingCartItem shoppingCa
return weight;
}
+ //TODO M1 PR 3: build the calculation context from PlaceOrderContainer instead of the ambient context
+ private CalculationContext AmbientCalculationContext =>
+ new(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency, _contextAccessor.WorkContext.TaxDisplayType);
+
protected virtual async Task PreparePlaceOrderDetails()
{
var details = new PlaceOrderContainer {
@@ -487,7 +492,7 @@ protected virtual async Task PreparePlaceOrderDetails()
//sub total
//sub total (incl tax)
- var shoppingCartSubTotal = await _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, true);
+ var shoppingCartSubTotal = await _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, AmbientCalculationContext, true);
var orderSubTotalDiscountAmount = shoppingCartSubTotal.discountAmount;
var orderSubTotalAppliedDiscounts = shoppingCartSubTotal.appliedDiscounts;
var subTotalWithoutDiscountBase = shoppingCartSubTotal.subTotalWithoutDiscount;
@@ -500,7 +505,7 @@ protected virtual async Task PreparePlaceOrderDetails()
details.AppliedDiscounts.Add(disc);
//sub total (excl tax)
- shoppingCartSubTotal = await _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, false);
+ shoppingCartSubTotal = await _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, AmbientCalculationContext, false);
orderSubTotalDiscountAmount = shoppingCartSubTotal.discountAmount;
subTotalWithoutDiscountBase = shoppingCartSubTotal.subTotalWithoutDiscount;
@@ -555,9 +560,9 @@ protected virtual async Task PreparePlaceOrderDetails()
//shipping total
var (orderShippingTotalInclTax, _, shippingTotalDiscounts) =
- await _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, true);
+ await _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, AmbientCalculationContext, true);
var orderShippingTotalExclTax =
- (await _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, false))
+ (await _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, AmbientCalculationContext, false))
.shoppingCartShippingTotal;
if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue)
throw new GrandException("Shipping total couldn't be calculated");
@@ -584,7 +589,7 @@ protected virtual async Task PreparePlaceOrderDetails()
//tax total
//tax amount
- var (taxTotal, taxRates) = await _orderTotalCalculationService.GetTaxTotal(details.Cart);
+ var (taxTotal, taxRates) = await _orderTotalCalculationService.GetTaxTotal(details.Cart, AmbientCalculationContext);
details.OrderTaxTotal = taxTotal;
//tax rates
@@ -596,7 +601,7 @@ protected virtual async Task PreparePlaceOrderDetails()
//order total (and applied discounts, gift vouchers, loyalty points)
var (orderTotal, orderDiscountAmount, orderAppliedDiscounts, appliedGiftVouchers, redeemedLoyaltyPoints,
- redeemedLoyaltyPointsAmount) = await _orderTotalCalculationService.GetShoppingCartTotal(details.Cart);
+ redeemedLoyaltyPointsAmount) = await _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, AmbientCalculationContext);
if (!orderTotal.HasValue)
throw new GrandException("Order total couldn't be calculated");
@@ -617,10 +622,16 @@ protected virtual async Task PreparePlaceOrderDetails()
protected virtual async Task PrepareOrderItem(ShoppingCartItem sc, Product product,
PlaceOrderContainer details)
{
- var scUnitPrice = (await _pricingService.GetUnitPrice(sc, product)).unitprice;
- var scUnitPriceWithoutDisc = (await _pricingService.GetUnitPrice(sc, product, false)).unitprice;
-
- var (scSubTotal, discountAmount, scDiscounts) = await _pricingService.GetSubTotal(sc, product);
+ var scUnitPrice = (await _pricingService.GetUnitPrice(sc, product,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).unitprice;
+ var scUnitPriceWithoutDisc = (await _pricingService.GetUnitPrice(sc, product,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency, false)).unitprice;
+
+ var (scSubTotal, discountAmount, scDiscounts) = await _pricingService.GetSubTotal(sc, product,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency);
var prices = await _taxService.GetTaxProductPrice(product, details.Customer,
details.Store, scUnitPrice,
diff --git a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandler.cs b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandler.cs
index eef41cfe94..775b77add1 100644
--- a/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandler.cs
+++ b/src/Business/Grand.Business.Checkout/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandler.cs
@@ -1,5 +1,6 @@
using Grand.Business.Core.Commands.Checkout.Orders;
using Grand.Business.Core.Interfaces.Checkout.Orders;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Orders;
using Grand.Mediator;
@@ -24,12 +25,13 @@ public async Task Handle(ValidateMinShoppingCartSubtotalAmountCommand requ
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request.Cart);
- ArgumentNullException.ThrowIfNull(request.Customer);
+ ArgumentNullException.ThrowIfNull(request.Context);
- return await ValidateMinOrderSubtotalAmount(request.Cart);
+ return await ValidateMinOrderSubtotalAmount(request.Cart, request.Context);
}
- protected virtual async Task ValidateMinOrderSubtotalAmount(IList cart)
+ protected virtual async Task ValidateMinOrderSubtotalAmount(IList cart,
+ CalculationContext context)
{
ArgumentNullException.ThrowIfNull(cart);
@@ -41,7 +43,7 @@ protected virtual async Task ValidateMinOrderSubtotalAmount(IList FormatAttributes(IList custom
if (renderPrices)
{
var priceAdjustmentBase =
- (await _taxService.GetCheckoutAttributePrice(attribute, attributeValue, customer, store))
+ (await _taxService.GetCheckoutAttributePrice(attribute, attributeValue,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, store))
.checkoutPrice;
var priceAdjustment =
await _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase,
_contextAccessor.WorkContext.WorkingCurrency);
if (priceAdjustmentBase > 0)
{
- var priceAdjustmentStr = _priceFormatter.FormatPrice(priceAdjustment);
+ var priceAdjustmentStr = _priceFormatter.FormatPrice(priceAdjustment,
+ _contextAccessor.WorkContext.WorkingCurrency);
formattedAttribute += $" [+{priceAdjustmentStr}]";
}
}
diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/OrderCalculationService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/OrderCalculationService.cs
index 42c6842b16..a3be2f49d6 100644
--- a/src/Business/Grand.Business.Checkout/Services/Orders/OrderCalculationService.cs
+++ b/src/Business/Grand.Business.Checkout/Services/Orders/OrderCalculationService.cs
@@ -17,7 +17,6 @@
using Grand.Domain.Shipping;
using Grand.Domain.Stores;
using Grand.Domain.Tax;
-using Grand.Infrastructure;
using Grand.SharedKernel;
namespace Grand.Business.Checkout.Services.Orders;
@@ -32,7 +31,6 @@ public class OrderCalculationService : IOrderCalculationService
///
/// Ctor
///
- /// Work context
/// Shopping cart subtotal service
/// Tax service
/// Shipping service
@@ -48,8 +46,7 @@ public class OrderCalculationService : IOrderCalculationService
/// Shipping settings
/// Shopping cart settings
/// Catalog settings
- public OrderCalculationService(IContextAccessor contextAccessor,
- IShoppingCartSubTotalService shoppingCartSubTotalService,
+ public OrderCalculationService(IShoppingCartSubTotalService shoppingCartSubTotalService,
ITaxService taxService,
IShippingService shippingService,
IPaymentService paymentService,
@@ -65,7 +62,6 @@ public OrderCalculationService(IContextAccessor contextAccessor,
ShoppingCartSettings shoppingCartSettings,
CatalogSettings catalogSettings)
{
- _contextAccessor = contextAccessor;
_shoppingCartSubTotalService = shoppingCartSubTotalService;
_taxService = taxService;
_shippingService = shippingService;
@@ -87,7 +83,6 @@ public OrderCalculationService(IContextAccessor contextAccessor,
#region Fields
- private readonly IContextAccessor _contextAccessor;
private readonly IShoppingCartSubTotalService _shoppingCartSubTotalService;
private readonly ITaxService _taxService;
private readonly IShippingService _shippingService;
@@ -125,7 +120,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
return (shippingDiscountAmount, appliedDiscounts);
var allDiscounts = await _discountService.GetActiveDiscountsByContext(DiscountType.AssignedToShipping,
- _contextAccessor.StoreContext.CurrentStore.Id, currency.CurrencyCode);
+ store.Id, currency.CurrencyCode);
var allowedDiscounts = new List();
if (allDiscounts != null)
foreach (var discount in allDiscounts)
@@ -144,7 +139,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
}
var (appliedDiscount, discountAmount) = await _discountService.GetPreferredDiscount(allowedDiscounts,
- customer, _contextAccessor.WorkContext.WorkingCurrency, shippingTotal);
+ customer, currency, shippingTotal);
appliedDiscounts = appliedDiscount;
shippingDiscountAmount = discountAmount;
@@ -153,7 +148,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
if (_shoppingCartSettings.RoundPrices)
shippingDiscountAmount =
- RoundingHelper.RoundPrice(shippingDiscountAmount, _contextAccessor.WorkContext.WorkingCurrency);
+ RoundingHelper.RoundPrice(shippingDiscountAmount, currency);
return (shippingDiscountAmount, appliedDiscounts);
}
@@ -175,7 +170,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
return (discountAmount, appliedDiscounts);
var allDiscounts = await _discountService.GetActiveDiscountsByContext(DiscountType.AssignedToOrderTotal,
- _contextAccessor.StoreContext.CurrentStore.Id, currency.CurrencyCode);
+ store.Id, currency.CurrencyCode);
var allowedDiscounts = new List();
if (allDiscounts != null)
foreach (var discount in allDiscounts)
@@ -194,7 +189,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
}
var preferredDiscount = await _discountService.GetPreferredDiscount(allowedDiscounts, customer,
- _contextAccessor.WorkContext.WorkingCurrency, orderTotal);
+ currency, orderTotal);
appliedDiscounts = preferredDiscount.appliedDiscount;
discountAmount = preferredDiscount.discountAmount;
@@ -202,7 +197,7 @@ public OrderCalculationService(IContextAccessor contextAccessor,
discountAmount = 0;
if (_shoppingCartSettings.RoundPrices)
- discountAmount = RoundingHelper.RoundPrice(discountAmount, _contextAccessor.WorkContext.WorkingCurrency);
+ discountAmount = RoundingHelper.RoundPrice(discountAmount, currency);
return (discountAmount, appliedDiscounts);
}
@@ -239,6 +234,7 @@ private async Task> GetActiveGiftVouchers(Customer customer,
/// Gets shopping cart subtotal
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether calculated price should include tax
///
/// Not virtual: payment providers read the subtotal from ,
@@ -247,9 +243,9 @@ private async Task> GetActiveGiftVouchers(Customer customer,
///
public Task<(double discountAmount, List appliedDiscounts,
double subTotalWithoutDiscount, double subTotalWithDiscount, SortedDictionary taxRates)>
- GetShoppingCartSubTotal(IList cart, bool includingTax)
+ GetShoppingCartSubTotal(IList cart, CalculationContext context, bool includingTax)
{
- return _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart, includingTax);
+ return _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart, context, includingTax);
}
@@ -257,10 +253,12 @@ private async Task> GetActiveGiftVouchers(Customer customer,
/// Gets shopping cart additional shipping charge
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Additional shipping charge
- public virtual async Task GetShoppingCartAdditionalShippingCharge(IList cart)
+ public virtual async Task GetShoppingCartAdditionalShippingCharge(IList cart,
+ CalculationContext context)
{
- var isFreeShipping = await IsFreeShipping(cart);
+ var isFreeShipping = await IsFreeShipping(cart, context);
if (isFreeShipping)
return 0;
@@ -274,10 +272,11 @@ public virtual async Task GetShoppingCartAdditionalShippingCharge(IList<
/// Gets a value indicating whether shipping is free
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether shipping is free
- public virtual async Task IsFreeShipping(IList cart)
+ public virtual async Task IsFreeShipping(IList cart, CalculationContext context)
{
- var customer = _contextAccessor.WorkContext.CurrentCustomer;
+ var customer = context.Customer;
if (customer != null)
{
//check whether customer has a free shipping
@@ -302,7 +301,7 @@ public virtual async Task IsFreeShipping(IList cart)
if (!_shippingSettings.FreeShippingOverXEnabled) return false;
//check whether we have subtotal enough to have free shipping
var (_, _, _, subTotalWithDiscount, _) =
- await GetShoppingCartSubTotal(cart, _shippingSettings.FreeShippingOverXIncludingTax);
+ await GetShoppingCartSubTotal(cart, context, _shippingSettings.FreeShippingOverXIncludingTax);
return subTotalWithDiscount > _shippingSettings.FreeShippingOverXValue;
//otherwise, return false
@@ -313,25 +312,26 @@ public virtual async Task IsFreeShipping(IList cart)
///
/// Shipping rate to adjust
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Adjusted shipping rate
public virtual async Task<(double shippingRate, List appliedDiscounts)> AdjustShippingRate(
- double shippingRate, IList cart)
+ double shippingRate, IList cart, CalculationContext context)
{
var appliedDiscounts = new List();
//free shipping
- if (await IsFreeShipping(cart))
+ if (await IsFreeShipping(cart, context))
return (0, appliedDiscounts);
//additional shipping charges
- var additionalShippingCharge = await GetShoppingCartAdditionalShippingCharge(cart);
+ var additionalShippingCharge = await GetShoppingCartAdditionalShippingCharge(cart, context);
var adjustedRate = shippingRate + additionalShippingCharge;
//discount
var (discountAmount, applyDiscounts) = await GetShippingDiscount(
- _contextAccessor.WorkContext.CurrentCustomer,
- _contextAccessor.StoreContext.CurrentStore,
- _contextAccessor.WorkContext.WorkingCurrency, adjustedRate);
+ context.Customer,
+ context.Store,
+ context.Currency, adjustedRate);
appliedDiscounts = applyDiscounts;
adjustedRate -= discountAmount;
@@ -340,7 +340,7 @@ public virtual async Task IsFreeShipping(IList cart)
adjustedRate = 0;
if (_shoppingCartSettings.RoundPrices)
- adjustedRate = RoundingHelper.RoundPrice(adjustedRate, _contextAccessor.WorkContext.WorkingCurrency);
+ adjustedRate = RoundingHelper.RoundPrice(adjustedRate, context.Currency);
return (adjustedRate, appliedDiscounts);
}
@@ -349,13 +349,14 @@ public virtual async Task IsFreeShipping(IList cart)
/// Gets shopping cart shipping total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Shipping total
public virtual async
Task<(double? shoppingCartShippingTotal, double taxRate, List appliedDiscounts)>
- GetShoppingCartShippingTotal(IList cart)
+ GetShoppingCartShippingTotal(IList cart, CalculationContext context)
{
- var includingTax = _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax;
- return await GetShoppingCartShippingTotal(cart, includingTax);
+ var includingTax = context.IncludingTax;
+ return await GetShoppingCartShippingTotal(cart, context, includingTax);
}
@@ -363,21 +364,22 @@ public virtual async
/// Gets shopping cart shipping total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether calculated price should include tax
/// Shipping total
public virtual async
Task<(double? shoppingCartShippingTotal, double taxRate, List appliedDiscounts)>
- GetShoppingCartShippingTotal(IList cart, bool includingTax)
+ GetShoppingCartShippingTotal(IList cart, CalculationContext context, bool includingTax)
{
double? shippingTotal = null;
var appliedDiscounts = new List();
double taxRate = 0;
- var customer = _contextAccessor.WorkContext.CurrentCustomer;
- var store = _contextAccessor.StoreContext.CurrentStore;
+ var customer = context.Customer;
+ var store = context.Store;
var currency = await _currencyService.GetPrimaryExchangeRateCurrency();
- var isFreeShipping = await IsFreeShipping(cart);
+ var isFreeShipping = await IsFreeShipping(cart, context);
if (isFreeShipping)
return (0, taxRate, appliedDiscounts);
@@ -385,12 +387,12 @@ public virtual async
if (customer != null)
shippingOption =
customer.GetUserFieldFromEntity(SystemCustomerFieldNames.SelectedShippingOption,
- _contextAccessor.StoreContext.CurrentStore.Id);
+ context.Store.Id);
if (shippingOption != null)
{
var rate = shippingOption.Rate;
- var adjustShippingRate = await AdjustShippingRate(rate, cart);
+ var adjustShippingRate = await AdjustShippingRate(rate, cart, context);
shippingTotal = adjustShippingRate.shippingRate;
appliedDiscounts = adjustShippingRate.appliedDiscounts;
}
@@ -402,8 +404,8 @@ public virtual async
shippingAddress = customer.ShippingAddress;
var shippingRateMethods =
- await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccessor.WorkContext.CurrentCustomer,
- _contextAccessor.StoreContext.CurrentStore.Id, cart);
+ await _shippingService.LoadActiveShippingRateCalculationProviders(context.Customer,
+ context.Store.Id, cart);
if (!shippingRateMethods.Any() && !_shippingSettings.AllowPickUpInStore)
throw new GrandException("Shipping rate method could not be loaded");
@@ -414,7 +416,7 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
var shippingOptionRequest = await _shippingService.CreateShippingOptionRequests(customer, cart,
shippingAddress,
- _contextAccessor.StoreContext.CurrentStore);
+ context.Store);
double? fixedRate = null;
//calculate fixed rates for each request-package
@@ -428,7 +430,7 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
if (fixedRate.HasValue)
{
//adjust shipping rate
- var adjustShippingRate = await AdjustShippingRate(fixedRate.Value, cart);
+ var adjustShippingRate = await AdjustShippingRate(fixedRate.Value, cart, context);
shippingTotal = adjustShippingRate.shippingRate;
appliedDiscounts = adjustShippingRate.appliedDiscounts;
}
@@ -463,31 +465,32 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
/// Gets tax
///
/// Shopping cart
+ /// Customer, store, currency and tax display the calculation is for
///
/// A value indicating whether we should use payment method additional fee when
/// calculating tax
///
/// Tax total
public virtual async Task<(double taxtotal, SortedDictionary taxRates)> GetTaxTotal(
- IList cart, bool usePaymentMethodAdditionalFee = true)
+ IList cart, CalculationContext context, bool usePaymentMethodAdditionalFee = true)
{
ArgumentNullException.ThrowIfNull(cart);
var taxRates = new SortedDictionary();
- var customer = _contextAccessor.WorkContext.CurrentCustomer;
- var store = _contextAccessor.StoreContext.CurrentStore;
+ var customer = context.Customer;
+ var store = context.Store;
var paymentMethodSystemName = "";
if (customer != null)
paymentMethodSystemName = customer.GetUserFieldFromEntity(
SystemCustomerFieldNames.SelectedPaymentMethod,
- _contextAccessor.StoreContext.CurrentStore.Id);
+ context.Store.Id);
//order sub total (items + checkout attributes)
double subTotalTaxTotal = 0;
- var shoppingCartSubTotal = await GetShoppingCartSubTotal(cart, false);
+ var shoppingCartSubTotal = await GetShoppingCartSubTotal(cart, context, false);
var orderSubTotalTaxRates = shoppingCartSubTotal.taxRates;
foreach (var (taxRate, taxValue) in orderSubTotalTaxRates)
@@ -505,10 +508,10 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
double shippingTax = 0;
if (_taxSettings.ShippingIsTaxable)
{
- var shippingTotalExcl = await GetShoppingCartShippingTotal(cart, false);
+ var shippingTotalExcl = await GetShoppingCartShippingTotal(cart, context, false);
var shippingExclTax = shippingTotalExcl.shoppingCartShippingTotal;
- var (shippingInclTax, taxRate, _) = await GetShoppingCartShippingTotal(cart, true);
+ var (shippingInclTax, taxRate, _) = await GetShoppingCartShippingTotal(cart, context, true);
if (shippingExclTax.HasValue && shippingInclTax.HasValue)
{
@@ -567,7 +570,7 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
taxTotal = 0;
//round tax
if (_shoppingCartSettings.RoundPrices)
- taxTotal = RoundingHelper.RoundPrice(taxTotal, _contextAccessor.WorkContext.WorkingCurrency);
+ taxTotal = RoundingHelper.RoundPrice(taxTotal, context.Currency);
return (taxTotal, taxRates);
}
@@ -577,28 +580,29 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
/// Gets shopping cart total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Use Loyalty Points
/// A value indicating whether we should use payment method additional fee
/// Shopping cart total, discount amount, applied discounts, gift vouchers, loyalty point/amount
public virtual async Task<(double? shoppingCartTotal, double discountAmount, List
appliedDiscounts, List appliedGiftVouchers,
int redeemedLoyaltyPoints, double redeemedLoyaltyPointsAmount)>
- GetShoppingCartTotal(IList cart, bool? useLoyaltyPoints = null,
+ GetShoppingCartTotal(IList cart, CalculationContext context, bool? useLoyaltyPoints = null,
bool usePaymentMethodAdditionalFee = true)
{
var redeemedLoyaltyPoints = 0;
double redeemedLoyaltyPointsAmount = 0;
- var paymentMethodSystemName = _contextAccessor.WorkContext.CurrentCustomer.GetUserFieldFromEntity(
+ var paymentMethodSystemName = context.Customer.GetUserFieldFromEntity(
SystemCustomerFieldNames.SelectedPaymentMethod,
- _contextAccessor.StoreContext.CurrentStore.Id);
+ context.Store.Id);
//subtotal without tax
- var subTotal = await GetShoppingCartSubTotal(cart, false);
+ var subTotal = await GetShoppingCartSubTotal(cart, context, false);
var subTotalWithDiscountBase = subTotal.subTotalWithDiscount;
//shipping without tax
- var shippingTotal = await GetShoppingCartShippingTotal(cart, false);
+ var shippingTotal = await GetShoppingCartShippingTotal(cart, context, false);
var shoppingCartShipping = shippingTotal.shoppingCartShippingTotal;
//payment method additional fee without tax
@@ -609,12 +613,12 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
await _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName);
paymentMethodAdditionalFeeWithoutTax =
(await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false,
- _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore))
+ context.Customer, context.Store))
.paymentPrice;
}
//tax
- var shoppingCartTax = (await GetTaxTotal(cart, usePaymentMethodAdditionalFee)).taxtotal;
+ var shoppingCartTax = (await GetTaxTotal(cart, context, usePaymentMethodAdditionalFee)).taxtotal;
//order total
double resultTemp = 0;
@@ -624,12 +628,12 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
resultTemp += paymentMethodAdditionalFeeWithoutTax;
resultTemp += shoppingCartTax;
if (_shoppingCartSettings.RoundPrices)
- resultTemp = RoundingHelper.RoundPrice(resultTemp, _contextAccessor.WorkContext.WorkingCurrency);
+ resultTemp = RoundingHelper.RoundPrice(resultTemp, context.Currency);
#region Order total discount
- var totalDiscount = await GetOrderTotalDiscount(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
- _contextAccessor.WorkContext.WorkingCurrency, resultTemp);
+ var totalDiscount = await GetOrderTotalDiscount(context.Customer, context.Store,
+ context.Currency, resultTemp);
var discountAmount = totalDiscount.orderTotalDiscount;
var appliedDiscounts = totalDiscount.appliedDiscounts;
@@ -643,7 +647,7 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
if (resultTemp < 0)
resultTemp = 0;
if (_shoppingCartSettings.RoundPrices)
- resultTemp = RoundingHelper.RoundPrice(resultTemp, _contextAccessor.WorkContext.WorkingCurrency);
+ resultTemp = RoundingHelper.RoundPrice(resultTemp, context.Currency);
#endregion
@@ -652,8 +656,8 @@ await _shippingService.LoadActiveShippingRateCalculationProviders(_contextAccess
var appliedGiftVouchers = new List();
//we don't apply gift vouchers for recurring products
var giftVouchers =
- await GetActiveGiftVouchers(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.WorkContext.WorkingCurrency,
- _contextAccessor.StoreContext.CurrentStore);
+ await GetActiveGiftVouchers(context.Customer, context.Currency,
+ context.Store);
if (giftVouchers != null)
foreach (var gc in giftVouchers)
if (resultTemp > 0)
@@ -676,7 +680,7 @@ await GetActiveGiftVouchers(_contextAccessor.WorkContext.CurrentCustomer, _conte
if (resultTemp < 0)
resultTemp = 0;
if (_shoppingCartSettings.RoundPrices)
- resultTemp = RoundingHelper.RoundPrice(resultTemp, _contextAccessor.WorkContext.WorkingCurrency);
+ resultTemp = RoundingHelper.RoundPrice(resultTemp, context.Currency);
if (!shoppingCartShipping.HasValue)
//we have errors
@@ -689,15 +693,15 @@ await GetActiveGiftVouchers(_contextAccessor.WorkContext.CurrentCustomer, _conte
if (_loyaltyPointsSettings.Enabled)
{
- useLoyaltyPoints ??= _contextAccessor.WorkContext.CurrentCustomer.GetUserFieldFromEntity(
+ useLoyaltyPoints ??= context.Customer.GetUserFieldFromEntity(
SystemCustomerFieldNames.UseLoyaltyPointsDuringCheckout,
- _contextAccessor.StoreContext.CurrentStore.Id);
+ context.Store.Id);
if (useLoyaltyPoints.Value)
{
var loyaltyPointsBalance =
- await _loyaltyPointsService.GetLoyaltyPointsBalance(_contextAccessor.WorkContext.CurrentCustomer.Id,
- _contextAccessor.StoreContext.CurrentStore.Id);
+ await _loyaltyPointsService.GetLoyaltyPointsBalance(context.Customer.Id,
+ context.Store.Id);
if (CheckMinimumLoyaltyPointsToUseRequirement(loyaltyPointsBalance))
{
var loyaltyPointsBalanceAmount = await ConvertLoyaltyPointsToAmount(loyaltyPointsBalance);
@@ -708,14 +712,14 @@ await _loyaltyPointsService.GetLoyaltyPointsBalance(_contextAccessor.WorkContext
redeemedLoyaltyPoints = loyaltyPointsBalance;
redeemedLoyaltyPointsAmount =
await _currencyService.ConvertFromPrimaryStoreCurrency(loyaltyPointsBalanceAmount,
- _contextAccessor.WorkContext.WorkingCurrency);
+ context.Currency);
}
else
{
redeemedLoyaltyPointsAmount = orderTotal;
redeemedLoyaltyPoints = ConvertAmountToLoyaltyPoints(
await _currencyService.ConvertToPrimaryStoreCurrency(redeemedLoyaltyPointsAmount,
- _contextAccessor.WorkContext.WorkingCurrency));
+ context.Currency));
}
}
}
@@ -726,7 +730,7 @@ await _currencyService.ConvertToPrimaryStoreCurrency(redeemedLoyaltyPointsAmount
orderTotal -= redeemedLoyaltyPointsAmount;
if (_shoppingCartSettings.RoundPrices)
- orderTotal = RoundingHelper.RoundPrice(orderTotal, _contextAccessor.WorkContext.WorkingCurrency);
+ orderTotal = RoundingHelper.RoundPrice(orderTotal, context.Currency);
return (orderTotal, discountAmount, appliedDiscounts, appliedGiftVouchers, redeemedLoyaltyPoints,
redeemedLoyaltyPointsAmount);
@@ -742,10 +746,12 @@ public virtual async Task ConvertLoyaltyPointsToAmount(int loyaltyPoints
if (loyaltyPoints <= 0)
return 0;
+ //the amount is in the primary store currency, so it is rounded in that currency
var result = loyaltyPoints * _loyaltyPointsSettings.ExchangeRate;
- if (_shoppingCartSettings.RoundPrices) result = RoundingHelper.RoundPrice(result, _contextAccessor.WorkContext.WorkingCurrency);
+ if (_shoppingCartSettings.RoundPrices)
+ result = RoundingHelper.RoundPrice(result, await _currencyService.GetPrimaryStoreCurrency());
- return await Task.FromResult(result);
+ return result;
}
///
diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartSubTotalService.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartSubTotalService.cs
index 9ad776e112..3849d91c87 100644
--- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartSubTotalService.cs
+++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartSubTotalService.cs
@@ -7,6 +7,7 @@
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Business.Core.Utilities.Catalog;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Catalog;
using Grand.Domain.Common;
using Grand.Domain.Customers;
@@ -14,7 +15,6 @@
using Grand.Domain.Discounts;
using Grand.Domain.Orders;
using Grand.Domain.Stores;
-using Grand.Infrastructure;
namespace Grand.Business.Checkout.Services.Orders;
@@ -25,8 +25,7 @@ public class ShoppingCartSubTotalService : IShoppingCartSubTotalService
{
#region Ctor
- public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
- IPricingService pricingService,
+ public ShoppingCartSubTotalService(IPricingService pricingService,
ITaxService taxService,
ICheckoutAttributeParser checkoutAttributeParser,
IDiscountService discountService,
@@ -36,7 +35,6 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
ShoppingCartSettings shoppingCartSettings,
CatalogSettings catalogSettings)
{
- _contextAccessor = contextAccessor;
_pricingService = pricingService;
_taxService = taxService;
_checkoutAttributeParser = checkoutAttributeParser;
@@ -52,7 +50,6 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
#region Fields
- private readonly IContextAccessor _contextAccessor;
private readonly IPricingService _pricingService;
private readonly ITaxService _taxService;
private readonly ICheckoutAttributeParser _checkoutAttributeParser;
@@ -84,7 +81,7 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
return (discountAmount, appliedDiscounts);
var allDiscounts = await _discountService.GetActiveDiscountsByContext(DiscountType.AssignedToOrderSubTotal,
- _contextAccessor.StoreContext.CurrentStore.Id, currency.CurrencyCode);
+ store.Id, currency.CurrencyCode);
var allowedDiscounts = new List();
if (allDiscounts != null)
foreach (var discount in allDiscounts)
@@ -103,7 +100,7 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
}
var preferredDiscounts = await _discountService.GetPreferredDiscount(allowedDiscounts, customer,
- _contextAccessor.WorkContext.WorkingCurrency, orderSubTotal);
+ currency, orderSubTotal);
appliedDiscounts = preferredDiscounts.appliedDiscount;
discountAmount = preferredDiscounts.discountAmount;
@@ -121,10 +118,11 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
/// Gets shopping cart subtotal
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether calculated price should include tax
public virtual async Task<(double discountAmount, List appliedDiscounts,
double subTotalWithoutDiscount, double subTotalWithDiscount, SortedDictionary taxRates)>
- GetShoppingCartSubTotal(IList cart, bool includingTax)
+ GetShoppingCartSubTotal(IList cart, CalculationContext context, bool includingTax)
{
double discountAmount = 0;
var appliedDiscounts = new List();
@@ -136,8 +134,8 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
return (discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
//get the customer
- var customer = _contextAccessor.WorkContext.CurrentCustomer;
- var store = _contextAccessor.StoreContext.CurrentStore;
+ var customer = context.Customer;
+ var store = context.Store;
//sub totals
double subTotalExclTaxWithoutDiscount = 0;
@@ -148,7 +146,8 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
if (product == null)
continue;
- var subtotal = await _pricingService.GetSubTotal(shoppingCartItem, product);
+ var subtotal = await _pricingService.GetSubTotal(shoppingCartItem, product,
+ customer, store, context.Currency);
var sciSubTotal = subtotal.subTotal;
var pricesExcl = await _taxService.GetProductPrice(product, sciSubTotal, false, customer, store);
@@ -177,9 +176,11 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
{
var checkoutAttributePriceExclTax = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, false, customer, store);
- var caExclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutAttributePriceExclTax.checkoutPrice, _contextAccessor.WorkContext.WorkingCurrency);
+ var caExclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutAttributePriceExclTax.checkoutPrice,
+ context.Currency);
var (checkoutPrice, taxRate) = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, true, customer, store);
- var caInclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutPrice, _contextAccessor.WorkContext.WorkingCurrency);
+ var caInclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutPrice,
+ context.Currency);
subTotalExclTaxWithoutDiscount += caExclTax;
subTotalInclTaxWithoutDiscount += caInclTax;
@@ -201,12 +202,13 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
subTotalWithoutDiscount = 0;
if (_shoppingCartSettings.RoundPrices)
- subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount, _contextAccessor.WorkContext.WorkingCurrency);
+ subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount,
+ context.Currency);
//We calculate discount amount on order subtotal excl tax (discount first)
//calculate discount amount ('Applied to order subtotal' discount)
- var orderSubtotalDiscount = await GetOrderSubtotalDiscount(customer, _contextAccessor.StoreContext.CurrentStore,
- _contextAccessor.WorkContext.WorkingCurrency, subTotalExclTaxWithoutDiscount);
+ var orderSubtotalDiscount = await GetOrderSubtotalDiscount(customer, store, context.Currency,
+ subTotalExclTaxWithoutDiscount);
var discountAmountExclTax = orderSubtotalDiscount.ordersubtotaldiscount;
appliedDiscounts = orderSubtotalDiscount.appliedDiscounts;
@@ -232,7 +234,7 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
discountAmountInclTax += discountTax;
taxValue = taxRates[taxRate] - discountTax;
if (_shoppingCartSettings.RoundPrices)
- taxValue = RoundingHelper.RoundPrice(taxValue, _contextAccessor.WorkContext.WorkingCurrency);
+ taxValue = RoundingHelper.RoundPrice(taxValue, context.Currency);
taxRates[taxRate] = taxValue;
}
@@ -242,8 +244,10 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
if (_shoppingCartSettings.RoundPrices)
{
- discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax, _contextAccessor.WorkContext.WorkingCurrency);
- discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax, _contextAccessor.WorkContext.WorkingCurrency);
+ discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax,
+ context.Currency);
+ discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax,
+ context.Currency);
}
if (includingTax)
@@ -261,7 +265,8 @@ public ShoppingCartSubTotalService(IContextAccessor contextAccessor,
subTotalWithDiscount = 0;
if (_shoppingCartSettings.RoundPrices)
- subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount, _contextAccessor.WorkContext.WorkingCurrency);
+ subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount,
+ context.Currency);
return (discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
}
diff --git a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs
index 92bc00b3a8..2094e071f6 100644
--- a/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs
+++ b/src/Business/Grand.Business.Checkout/Services/Orders/ShoppingCartValidator.cs
@@ -150,8 +150,11 @@ public virtual async Task> GetShoppingCartWarnings(IList().ValidateAsync(
- new ShoppingCartTotalAmountValidatorRecord(_contextAccessor.WorkContext.CurrentCustomer,
- _contextAccessor.WorkContext.WorkingCurrency, shoppingCart));
+ new ShoppingCartTotalAmountValidatorRecord(shoppingCart,
+ //TODO M1 PR 3: the cart validator still reads the ambient context
+ new CalculationContext(_contextAccessor.WorkContext.CurrentCustomer,
+ _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingCurrency,
+ _contextAccessor.WorkContext.TaxDisplayType)));
if (!resultCheckoutAttributes.IsValid)
warnings.AddRange(resultCheckoutAttributes.Errors.Select(x => x.ErrorMessage));
}
diff --git a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs
index 87823a2112..ae3966a703 100644
--- a/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs
+++ b/src/Business/Grand.Business.Checkout/Validators/ShoppingCartTotalAmountValidator.cs
@@ -3,16 +3,14 @@
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Business.Core.Interfaces.Common.Localization;
-using Grand.Domain.Customers;
-using Grand.Domain.Directory;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Orders;
namespace Grand.Business.Checkout.Validators;
public record ShoppingCartTotalAmountValidatorRecord(
- Customer Customer,
- Currency Currency,
- IList Cart);
+ IList Cart,
+ CalculationContext Context);
public class ShoppingCartTotalAmountValidator : AbstractValidator
{
@@ -26,7 +24,7 @@ public ShoppingCartTotalAmountValidator(
{
RuleFor(x => x).CustomAsync(async (value, context, _) =>
{
- var customerGroups = await groupService.GetAllByIds(value.Customer.Groups.ToArray());
+ var customerGroups = await groupService.GetAllByIds(value.Context.Customer.Groups.ToArray());
var minRoles = customerGroups.OrderBy(x => x.MinOrderAmount).FirstOrDefault(x => x.MinOrderAmount.HasValue);
var minOrderAmount = minRoles?.MinOrderAmount ?? 0;
@@ -38,7 +36,7 @@ public ShoppingCartTotalAmountValidator(
!(orderSettings.MinOrderTotalAmount > 0))) return;
var shoppingCartTotalBase =
- (await orderTotalCalculationService.GetShoppingCartTotal(value.Cart)).shoppingCartTotal;
+ (await orderTotalCalculationService.GetShoppingCartTotal(value.Cart, value.Context)).shoppingCartTotal;
if (shoppingCartTotalBase.HasValue && (shoppingCartTotalBase.Value < minOrderAmount ||
shoppingCartTotalBase.Value > maxOrderAmount))
@@ -48,14 +46,14 @@ public ShoppingCartTotalAmountValidator(
//subtotal
var (_, _, subTotalWithoutDiscount, _, _) =
- await orderTotalCalculationService.GetShoppingCartSubTotal(value.Cart, false);
+ await orderTotalCalculationService.GetShoppingCartSubTotal(value.Cart, value.Context, false);
if (subTotalWithoutDiscount < orderSettings.MinOrderSubtotalAmount)
{
var minOrderSubtotalAmount =
await currencyService.ConvertFromPrimaryStoreCurrency(orderSettings.MinOrderSubtotalAmount,
- value.Currency);
+ value.Context.Currency);
context.AddFailure(string.Format(translationService.GetResource("Checkout.MinOrderSubtotalAmount"),
- priceFormatter.FormatPrice(minOrderSubtotalAmount, value.Currency)));
+ priceFormatter.FormatPrice(minOrderSubtotalAmount, value.Context.Currency)));
}
});
}
diff --git a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ValidateMinShoppingCartSubtotalAmountCommand.cs b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ValidateMinShoppingCartSubtotalAmountCommand.cs
index 0a17cbd40e..1f69ec782e 100644
--- a/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ValidateMinShoppingCartSubtotalAmountCommand.cs
+++ b/src/Business/Grand.Business.Core/Commands/Checkout/Orders/ValidateMinShoppingCartSubtotalAmountCommand.cs
@@ -1,4 +1,4 @@
-using Grand.Domain.Customers;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Orders;
using Grand.Mediator;
@@ -6,6 +6,6 @@ namespace Grand.Business.Core.Commands.Checkout.Orders;
public class ValidateMinShoppingCartSubtotalAmountCommand : IRequest
{
- public Customer Customer { get; set; }
public IList Cart { get; set; }
+ public CalculationContext Context { get; set; }
}
\ No newline at end of file
diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPriceFormatter.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPriceFormatter.cs
index be5a8c06bf..de287f0899 100644
--- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPriceFormatter.cs
+++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPriceFormatter.cs
@@ -4,13 +4,6 @@ namespace Grand.Business.Core.Interfaces.Catalog.Prices;
public interface IPriceFormatter
{
- ///
- /// Formats price
- ///
- /// Price
- /// Price
- string FormatPrice(double price);
-
///
/// Formats price
///
diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPricingService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPricingService.cs
index 580a1caa9a..d95aacf1d7 100644
--- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPricingService.cs
+++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Prices/IPricingService.cs
@@ -64,12 +64,14 @@ public interface IPricingService
///
/// The shopping cart item
/// Product
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// A value indicating whether include discounts or not for price computation
/// Shopping cart unit price (one item)
Task<(double unitprice, double discountAmount, List appliedDiscounts)> GetUnitPrice(
- ShoppingCartItem shoppingCartItem,
- Product product, bool includeDiscounts = true);
-
+ ShoppingCartItem shoppingCartItem, Product product, Customer customer, Store store, Currency currency,
+ bool includeDiscounts = true);
///
/// Gets the shopping cart unit price (one item)
@@ -102,10 +104,13 @@ public interface IPricingService
///
/// The shopping cart item
/// Product
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// A value indicating whether include discounts or not for price computation
/// Shopping cart item sub total
Task<(double subTotal, double discountAmount, List appliedDiscounts)> GetSubTotal(
- ShoppingCartItem shoppingCartItem, Product product,
+ ShoppingCartItem shoppingCartItem, Product product, Customer customer, Store store, Currency currency,
bool includeDiscounts = true);
///
@@ -121,6 +126,10 @@ public interface IPricingService
/// Get a price adjustment of a product attribute value
///
/// Product attribute value
+ /// Customer the price is for
+ /// Store the price is for
+ /// Currency the price is converted to
/// Price adjustment
- Task GetProductAttributeValuePriceAdjustment(ProductAttributeValue value);
+ Task GetProductAttributeValuePriceAdjustment(ProductAttributeValue value, Customer customer, Store store,
+ Currency currency);
}
\ No newline at end of file
diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Tax/ITaxService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Tax/ITaxService.cs
index 9aa86712cd..f95373069f 100644
--- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Tax/ITaxService.cs
+++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Tax/ITaxService.cs
@@ -15,8 +15,10 @@ public interface ITaxService
///
/// Load active tax provider
///
+ /// Customer whose groups limit the fallback providers
+ /// Store whose providers are the fallback
/// Active tax provider
- ITaxProvider LoadActiveTaxProvider();
+ ITaxProvider LoadActiveTaxProvider(Customer customer, string storeId);
///
/// Load tax provider by system name
@@ -34,24 +36,6 @@ public interface ITaxService
IList LoadAllTaxProviders(Customer customer = null, string storeId = "");
- ///
- /// Gets price
- ///
- /// Product
- /// Price
- /// Price
- Task<(double productprice, double taxRate)> GetProductPrice(Product product, double price);
-
- ///
- /// Gets price
- ///
- /// Product
- /// Price
- /// Customer
- /// Store
- /// Price
- Task<(double productprice, double taxRate)> GetProductPrice(Product product, double price, Customer customer, Store store);
-
///
/// Gets price
///
@@ -103,15 +87,6 @@ Task GetTaxProductPrice(
bool priceIncludesTax
);
- ///
- /// Gets shipping price
- ///
- /// Price
- /// Customer
- /// Store
- /// Price
- Task<(double shippingPrice, double taxRate)> GetShippingPrice(double price, Customer customer, Store store);
-
///
/// Gets shipping price
///
@@ -122,15 +97,6 @@ bool priceIncludesTax
/// Price
Task<(double shippingPrice, double taxRate)> GetShippingPrice(double price, bool includingTax, Customer customer, Store store);
- ///
- /// Gets payment method additional handling fee
- ///
- /// Price
- /// Customer
- /// Store
- /// Price
- Task<(double paymentPrice, double taxRate)> GetPaymentMethodAdditionalFee(double price, Customer customer, Store store);
-
///
/// Gets payment method additional handling fee
///
@@ -141,24 +107,6 @@ bool priceIncludesTax
/// Price
Task<(double paymentPrice, double taxRate)> GetPaymentMethodAdditionalFee(double price, bool includingTax, Customer customer, Store store);
- ///
- /// Gets checkout attribute value price
- ///
- /// Checkout attribute
- /// Checkout attribute value
- /// Price
- Task<(double checkoutPrice, double taxRate)> GetCheckoutAttributePrice(CheckoutAttribute ca, CheckoutAttributeValue cav);
-
- ///
- /// Gets checkout attribute value price
- ///
- /// Checkout attribute
- /// Checkout attribute value
- /// Customer
- /// Store
- /// Price
- Task<(double checkoutPrice, double taxRate)> GetCheckoutAttributePrice(CheckoutAttribute ca, CheckoutAttributeValue cav, Customer customer, Store store);
-
///
/// Gets checkout attribute value price
///
diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IOrderCalculationService.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IOrderCalculationService.cs
index ffaa3d0a59..6f43c76239 100644
--- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IOrderCalculationService.cs
+++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IOrderCalculationService.cs
@@ -13,10 +13,11 @@ public interface IOrderCalculationService
/// Gets shopping cart subtotal
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether calculated price should include tax
Task<(double discountAmount, List appliedDiscounts, double subTotalWithoutDiscount, double
subTotalWithDiscount, SortedDictionary taxRates)>
- GetShoppingCartSubTotal(IList cart,
+ GetShoppingCartSubTotal(IList cart, CalculationContext context,
bool includingTax);
///
@@ -24,58 +25,65 @@ public interface IOrderCalculationService
///
/// Shipping rate to adjust
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Adjusted shipping rate
Task<(double shippingRate, List appliedDiscounts)> AdjustShippingRate(double shippingRate,
- IList cart);
+ IList cart, CalculationContext context);
///
/// Gets shopping cart additional shipping charge
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Additional shipping charge
- Task GetShoppingCartAdditionalShippingCharge(IList cart);
+ Task GetShoppingCartAdditionalShippingCharge(IList cart, CalculationContext context);
///
/// Gets a value indicating whether shipping is free
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether shipping is free
- Task IsFreeShipping(IList cart);
+ Task IsFreeShipping(IList cart, CalculationContext context);
///
/// Gets shopping cart shipping total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// Shipping total
Task<(double? shoppingCartShippingTotal, double taxRate, List appliedDiscounts)>
- GetShoppingCartShippingTotal(IList cart);
+ GetShoppingCartShippingTotal(IList cart, CalculationContext context);
///
/// Gets shopping cart shipping total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
/// A value indicating whether calculated price should include tax
/// Shipping total
Task<(double? shoppingCartShippingTotal, double taxRate, List appliedDiscounts)>
- GetShoppingCartShippingTotal(IList cart, bool includingTax);
+ GetShoppingCartShippingTotal(IList cart, CalculationContext context, bool includingTax);
///
/// Gets tax
///
/// Shopping cart
+ /// Customer, store, currency and tax display the calculation is for
///
/// A value indicating whether we should use payment method additional fee when
/// calculating tax
///
/// Tax total
Task<(double taxtotal, SortedDictionary taxRates)> GetTaxTotal(IList cart,
- bool usePaymentMethodAdditionalFee = true);
+ CalculationContext context, bool usePaymentMethodAdditionalFee = true);
///
/// Gets shopping cart total
///
/// Cart
+ /// Customer, store, currency and tax display the calculation is for
///
/// A value indicating loyalty points should be used; null to detect current choice of the
/// customer
@@ -88,14 +96,14 @@ public interface IOrderCalculationService
Task<(double? shoppingCartTotal, double discountAmount, List appliedDiscounts,
List appliedGiftVouchers,
int redeemedLoyaltyPoints, double redeemedLoyaltyPointsAmount)>
- GetShoppingCartTotal(IList cart, bool? useLoyaltyPoints = null,
+ GetShoppingCartTotal(IList cart, CalculationContext context, bool? useLoyaltyPoints = null,
bool usePaymentMethodAdditionalFee = true);
///
/// Converts existing loyalty points to amount
///
/// Loyalty points
- /// Converted value
+ /// Converted value in the primary store currency
Task ConvertLoyaltyPointsToAmount(int loyaltyPoints);
///
diff --git a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartSubTotalService.cs b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartSubTotalService.cs
index 35cd4534b7..ddcda21540 100644
--- a/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartSubTotalService.cs
+++ b/src/Business/Grand.Business.Core/Interfaces/Checkout/Orders/IShoppingCartSubTotalService.cs
@@ -1,4 +1,5 @@
using Grand.Business.Core.Utilities.Catalog;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Orders;
namespace Grand.Business.Core.Interfaces.Checkout.Orders;
@@ -17,5 +18,5 @@ public interface IShoppingCartSubTotalService
/// A value indicating whether calculated price should include tax
Task<(double discountAmount, List appliedDiscounts, double subTotalWithoutDiscount, double
subTotalWithDiscount, SortedDictionary taxRates)>
- GetShoppingCartSubTotal(IList cart, bool includingTax);
+ GetShoppingCartSubTotal(IList cart, CalculationContext context, bool includingTax);
}
diff --git a/src/Business/Grand.Business.Core/Utilities/Checkout/CalculationContext.cs b/src/Business/Grand.Business.Core/Utilities/Checkout/CalculationContext.cs
new file mode 100644
index 0000000000..07db7de6a2
--- /dev/null
+++ b/src/Business/Grand.Business.Core/Utilities/Checkout/CalculationContext.cs
@@ -0,0 +1,18 @@
+using Grand.Domain.Customers;
+using Grand.Domain.Directory;
+using Grand.Domain.Stores;
+using Grand.Domain.Tax;
+
+namespace Grand.Business.Core.Utilities.Checkout;
+
+///
+/// Who and where a checkout calculation is for: the customer whose cart, discounts and loyalty points apply,
+/// the store, the currency amounts are rounded and converted in, and whether prices include tax
+///
+public sealed record CalculationContext(Customer Customer, Store Store, Currency Currency, TaxDisplayType TaxDisplayType)
+{
+ ///
+ /// Gets a value indicating whether prices are calculated including tax
+ ///
+ public bool IncludingTax => TaxDisplayType == TaxDisplayType.IncludingTax;
+}
diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs
index 2642d7bab6..fefe67d106 100644
--- a/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs
+++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Messages/GetOrderTokensCommandHandler.cs
@@ -136,7 +136,7 @@ public async Task Handle(GetOrderTokensCommand request, Cancellatio
foreach (var item in request.Order.OrderTaxes)
{
var taxRate = _priceFormatter.FormatTaxRate(item.Percent);
- var taxValue = _priceFormatter.FormatPrice(item.Amount);
+ var taxValue = _priceFormatter.FormatPrice(item.Amount, currency);
if (string.IsNullOrEmpty(taxRate))
taxRate = item.Percent.ToString(CultureInfo.InvariantCulture);
diff --git a/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs b/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs
index fc6eeddbe7..d744eaa64e 100644
--- a/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs
+++ b/src/Plugins/Payments.BrainTree/BrainTreePaymentProvider.cs
@@ -235,7 +235,9 @@ public async Task GetAdditionalHandlingFee(IList cart)
if (_brainTreePaymentSettings.AdditionalFeePercentage)
{
//percentage of the subtotal, which is already in the working currency
- var subtotal = await _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart, true);
+ var subtotal = await _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart,
+ new CalculationContext(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency, _contextAccessor.WorkContext.TaxDisplayType), true);
return (float)subtotal.subTotalWithDiscount * (float)_brainTreePaymentSettings.AdditionalFee / 100f;
}
diff --git a/src/Plugins/Payments.BrainTree/Controllers/PaymentBrainTreeController.cs b/src/Plugins/Payments.BrainTree/Controllers/PaymentBrainTreeController.cs
index 83a9291c59..d55e33e5fd 100644
--- a/src/Plugins/Payments.BrainTree/Controllers/PaymentBrainTreeController.cs
+++ b/src/Plugins/Payments.BrainTree/Controllers/PaymentBrainTreeController.cs
@@ -1,6 +1,7 @@
using Braintree;
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Common.Localization;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Orders;
using Grand.Infrastructure;
using Grand.Web.Common.Controllers;
@@ -60,7 +61,9 @@ public async Task PaymentInfo()
};
ViewBag.ClientToken = await gateway.ClientToken.GenerateAsync();
- ViewBag.OrderTotal = (await _orderTotalCalculationService.GetShoppingCartTotal(cart)).shoppingCartTotal;
+ ViewBag.OrderTotal = (await _orderTotalCalculationService.GetShoppingCartTotal(cart,
+ new CalculationContext(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency, _contextAccessor.WorkContext.TaxDisplayType))).shoppingCartTotal;
return View("PaymentInfo_3DS", model);
}
diff --git a/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs b/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs
index 7e8cde3bbe..69e432e8df 100644
--- a/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs
+++ b/src/Plugins/Payments.CashOnDelivery/CashOnDeliveryPaymentProvider.cs
@@ -96,7 +96,9 @@ public async Task GetAdditionalHandlingFee(IList cart)
if (_cashOnDeliveryPaymentSettings.AdditionalFeePercentage)
{
//percentage of the subtotal, which is already in the working currency
- var subtotal = await _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart, true);
+ var subtotal = await _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart,
+ new CalculationContext(_contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency, _contextAccessor.WorkContext.TaxDisplayType), true);
return (float)subtotal.subTotalWithDiscount * (float)_cashOnDeliveryPaymentSettings.AdditionalFee / 100f;
}
diff --git a/src/Plugins/Shipping.ByWeight/ByWeightShippingProvider.cs b/src/Plugins/Shipping.ByWeight/ByWeightShippingProvider.cs
index 5a0f8568c2..420e75f66d 100644
--- a/src/Plugins/Shipping.ByWeight/ByWeightShippingProvider.cs
+++ b/src/Plugins/Shipping.ByWeight/ByWeightShippingProvider.cs
@@ -220,12 +220,15 @@ public async Task GetShippingOptions(GetShippingOptio
var product = await _productService.GetProductById(packageItem.ShoppingCartItem.ProductId);
if (product != null)
- subTotal += (await _pricingService.GetSubTotal(packageItem.ShoppingCartItem, product)).subTotal;
+ subTotal += (await _pricingService.GetSubTotal(packageItem.ShoppingCartItem, product,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).subTotal;
}
var weight = await GetTotalWeight(getShippingOptionRequest);
- var shippingMethods = await _shippingMethodService.GetAllShippingMethods(countryId, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id);
+ var shippingMethods = await _shippingMethodService.GetAllShippingMethods(countryId,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id);
foreach (var shippingMethod in shippingMethods)
{
double? rate = null;
diff --git a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetShoppingCartItemsSubTotalQueryHandlerTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetShoppingCartItemsSubTotalQueryHandlerTests.cs
index 5bda515fef..6818f8cae2 100644
--- a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetShoppingCartItemsSubTotalQueryHandlerTests.cs
+++ b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetShoppingCartItemsSubTotalQueryHandlerTests.cs
@@ -48,7 +48,9 @@ public async Task Handle_PricesItemsForRequestCustomerInPrimaryCurrency_SkipsMis
}, CancellationToken.None);
Assert.AreEqual(60, subTotal);
- pricingService.Verify(p => p.GetSubTotal(It.IsAny(), It.IsAny(), It.IsAny()),
+ pricingService.Verify(p => p.GetSubTotal(It.IsAny(), It.IsAny(),
+ It.IsAny(), It.IsAny(), It.IsAny(),
+ It.IsAny()),
Times.Never);
}
}
diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceFormatterTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceFormatterTests.cs
index 3e4199f545..cde9b0d9e7 100644
--- a/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceFormatterTests.cs
+++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceFormatterTests.cs
@@ -99,7 +99,7 @@ public void TestInitialize()
_translationService = tempLocalizationService.Object;
}
- _priceFormatter = new PriceFormatter(tempWorkContext.Object);
+ _priceFormatter = new PriceFormatter();
}
[TestMethod]
diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceServiceTests.cs
index 279f027061..668309248c 100644
--- a/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceServiceTests.cs
+++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Prices/PriceServiceTests.cs
@@ -110,7 +110,6 @@ public void TestInitialize()
tempDiscountApplicationService = new Mock();
_pricingService = new PricingService(
- _workContext,
_productService,
_eventPublisher,
_currencyService,
@@ -291,10 +290,50 @@ public async Task Can_get_shopping_cart_item_unitPrice()
tempDiscountServiceMock
.Setup(x => x.GetActiveDiscountsByContext(DiscountType.AssignedToAllProducts, "1", _currency.CurrencyCode))
.ReturnsAsync(new List());
- var pp = (await _pricingService.GetUnitPrice(shoppingCartItem, product001)).unitprice;
+ var pp = (await _pricingService.GetUnitPrice(shoppingCartItem, product001, customer001, _store, _currency))
+ .unitprice;
Assert.AreEqual(49.99, pp);
}
+ [TestMethod]
+ public async Task GetSubTotal_PricesTheItemForTheGivenCustomerStoreAndCurrency()
+ {
+ //Arrange - nothing is set on the ambient context: the price must come from the arguments alone
+ var product = new Product { Id = "explicit", Name = "explicit", Price = 10, Published = true };
+ product.ProductPrices.Add(new ProductPrice { CurrencyCode = "USD", Price = 10 });
+ var customer = new Customer { Id = "c-explicit" };
+ var item = new ShoppingCartItem { ProductId = product.Id, Quantity = 3 };
+ tempDiscountApplicationService.Setup(x => x.GetDiscountAmount(It.IsAny(), customer, _store,
+ _currency, It.IsAny()))
+ .ReturnsAsync((new List(), 0));
+
+ //Act
+ var (subTotal, _, _) = await _pricingService.GetSubTotal(item, product, customer, _store, _currency);
+
+ //Assert
+ Assert.AreEqual(30, subTotal);
+ }
+
+ [TestMethod]
+ public async Task GetProductAttributeValuePriceAdjustment_ConvertsToTheGivenCurrency()
+ {
+ //Arrange - a working currency at twice the primary one, so the conversion shows
+ var eur = new Currency { Id = "2", CurrencyCode = "EUR", Rate = 2, Published = true,
+ MidpointRoundId = MidpointRounding.ToEven, RoundingTypeId = RoundingType.Rounding001 };
+ var value = new ProductAttributeValue { PriceAdjustment = 10, AttributeValueTypeId = AttributeValueType.Simple };
+ var currencyService = new Mock();
+ currencyService.Setup(x => x.ConvertFromPrimaryStoreCurrency(10, eur)).ReturnsAsync(20);
+ var pricingService = new PricingService(_productService, _eventPublisher, currencyService.Object,
+ tempDiscountApplicationService.Object, _shoppingCartSettings, _catalogSettings);
+
+ //Act
+ var adjustment =
+ await pricingService.GetProductAttributeValuePriceAdjustment(value, new Customer(), _store, eur);
+
+ //Assert
+ Assert.AreEqual(20, adjustment);
+ }
+
[TestMethod]
public async Task Can_get_shopping_cart_item_subTotal()
{
@@ -327,7 +366,8 @@ public async Task Can_get_shopping_cart_item_subTotal()
tempDiscountServiceMock
.Setup(x => x.GetActiveDiscountsByContext(DiscountType.AssignedToAllProducts, "1", _currency.CurrencyCode))
.ReturnsAsync(new List());
- var subtotal = (await _pricingService.GetSubTotal(shoppingCartItem, product001)).subTotal;
+ var subtotal = (await _pricingService.GetSubTotal(shoppingCartItem, product001, customer001, _store, _currency))
+ .subTotal;
Assert.AreEqual(110.22, subtotal);
}
@@ -350,7 +390,7 @@ public async Task GetSubTotal_DiscountedQuantityCap_OnlyAPositiveValueLimits(int
It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync((new List { new() { DiscountId = "d1", MaximumDiscountedQuantity = cap } }, 100));
- var (subTotal, _, _) = await _pricingService.GetSubTotal(item, product);
+ var (subTotal, _, _) = await _pricingService.GetSubTotal(item, product, customer, _store, _currency);
Assert.AreEqual(expected, subTotal);
}
diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Tax/TaxServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Tax/TaxServiceTests.cs
index dfb1bc42dd..4d4d2bf5b9 100644
--- a/src/Tests/Grand.Business.Catalog.Tests/Services/Tax/TaxServiceTests.cs
+++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Tax/TaxServiceTests.cs
@@ -50,13 +50,26 @@ public void TestInitialize()
_loggerMock = new Mock>();
var providers = new List();
providers.Add(new FixedRateTestTaxProvider());
- _taxService = new TaxService(_workContext, _groupServiceMock.Object,
+ _taxService = new TaxService(_groupServiceMock.Object,
_countryService, providers, _loggerMock.Object,
_taxSettings, _taxProviderSettings, _customerSettings, _addressSettings);
_vatService = new VatService(_taxSettings);
}
+ [TestMethod]
+ public void LoadActiveTaxProvider_FallsBackToProvidersOfGivenCustomerAndStore()
+ {
+ //Arrange
+ _taxProviderSettings.ActiveTaxProviderSystemName = "not-installed";
+
+ //Act
+ var provider = _taxService.LoadActiveTaxProvider(new Customer(), "store1");
+
+ //Assert
+ Assert.AreEqual("test-provider", provider?.SystemName);
+ }
+
[TestMethod]
public async Task Can_check_taxExempt_product()
{
diff --git a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandlerTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandlerTests.cs
index af4fcde676..4ca0cc2fcb 100644
--- a/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandlerTests.cs
+++ b/src/Tests/Grand.Business.Checkout.Tests/Commands/Handlers/Orders/ValidateMinShoppingCartSubtotalAmountCommandHandlerTests.cs
@@ -30,12 +30,14 @@ public void Init()
public async Task HandleTest()
{
var command = new ValidateMinShoppingCartSubtotalAmountCommand {
- Customer = new Customer(),
- Cart = new List { new() }
+ Cart = new List { new() },
+ Context = new Grand.Business.Core.Utilities.Checkout.CalculationContext(new Customer(),
+ new Grand.Domain.Stores.Store(), new Grand.Domain.Directory.Currency(),
+ Grand.Domain.Tax.TaxDisplayType.ExcludingTax)
};
_orderTotalCalculationServiceMock
- .Setup(x => x.GetShoppingCartSubTotal(It.IsAny>(), false)).Returns(() =>
+ .Setup(x => x.GetShoppingCartSubTotal(It.IsAny>(), It.IsAny(), false)).Returns(() =>
Task.FromResult<(double discountAmount, List appliedDiscounts, double
subTotalWithoutDiscount, double subTotalWithDiscount, SortedDictionary taxRates)>((
20, null, 100, 100, null)));
diff --git a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/OrderCalculationServiceTests.cs b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/OrderCalculationServiceTests.cs
index 243772127a..1f1ccb9d29 100644
--- a/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/OrderCalculationServiceTests.cs
+++ b/src/Tests/Grand.Business.Checkout.Tests/Services/Orders/OrderCalculationServiceTests.cs
@@ -9,6 +9,7 @@
using Grand.Business.Core.Interfaces.Checkout.Payments;
using Grand.Business.Core.Interfaces.Checkout.Shipping;
using Grand.Business.Core.Interfaces.Common.Directory;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Catalog;
using Grand.Domain.Common;
using Grand.Domain.Customers;
@@ -46,12 +47,11 @@ public class OrderCalculationServiceTests
private Mock _taxServiceMock;
private TaxSettings _taxSettings;
- private Mock _workContextMock;
+ private CalculationContext _context;
[TestInitialize]
public void Init()
{
- _workContextMock = new Mock();
_pricingServiceMock = new Mock();
_taxServiceMock = new Mock();
_shippingServiceMock = new Mock();
@@ -69,19 +69,17 @@ public void Init()
_shoppingCartSettings = new ShoppingCartSettings();
_catalogSettings = new CatalogSettings();
_discountValidationService = new Mock();
- _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "", Name = "test store" });
var customer = new Customer();
customer.Groups.Add("1");
- _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => customer);
- _workContextMock.Setup(c => c.WorkContext.WorkingCurrency).Returns(() => new Currency());
- _workContextMock.Setup(c => c.WorkContext.TaxDisplayType).Returns(() => TaxDisplayType.ExcludingTax);
+ _context = new CalculationContext(customer, new Store { Id = "", Name = "test store" }, new Currency(),
+ TaxDisplayType.ExcludingTax);
- var shoppingCartSubTotalService = new ShoppingCartSubTotalService(_workContextMock.Object,
+ var shoppingCartSubTotalService = new ShoppingCartSubTotalService(
_pricingServiceMock.Object, _taxServiceMock.Object, _checkoutAttributeParserMock.Object,
_discountServiceMock.Object, _discountValidationService.Object, _productServiceMock.Object,
_currencyServiceMock.Object, _shoppingCartSettings, _catalogSettings);
- _service = new OrderCalculationService(_workContextMock.Object, shoppingCartSubTotalService,
+ _service = new OrderCalculationService(shoppingCartSubTotalService,
_taxServiceMock.Object, _shippingServiceMock.Object,
_paymentServiceMock.Object, _discountServiceMock.Object,
_giftVoucherServiceMock.Object,
@@ -90,6 +88,43 @@ public void Init()
_taxSettings, _loyaltyPointsSettings, _shippingSettings, _shoppingCartSettings, _catalogSettings);
}
+ [TestMethod]
+ public async Task IsFreeShipping_UsesTheCustomerOfTheContext()
+ {
+ //Arrange
+ var customer = new Customer { FreeShipping = true };
+ var context = new CalculationContext(customer, new Store { Id = "s1" }, new Currency(),
+ TaxDisplayType.ExcludingTax);
+
+ //Act
+ var free = await _service.IsFreeShipping(new List { new() { IsShipEnabled = true } },
+ context);
+
+ //Assert
+ Assert.IsTrue(free);
+ }
+
+ [TestMethod]
+ public async Task GetShoppingCartSubTotal_PricesItemsForTheContextCustomerStoreAndCurrency()
+ {
+ //Arrange
+ var product = new Product { Id = "1", Published = true, Price = 10 };
+ _productServiceMock.Setup(a => a.GetProductById(It.IsAny(), false)).ReturnsAsync(product);
+ _checkoutAttributeParserMock.Setup(a => a.ParseCheckoutAttributeValue(It.IsAny>()))
+ .ReturnsAsync(new List<(CheckoutAttribute ca, CheckoutAttributeValue cav)>());
+ var customer = new Customer();
+ var store = new Store { Id = "s1" };
+ var currency = new Currency { CurrencyCode = "EUR" };
+ var context = new CalculationContext(customer, store, currency, TaxDisplayType.ExcludingTax);
+
+ //Act
+ await _service.GetShoppingCartSubTotal(new List { new() { Quantity = 1 } }, context, false);
+
+ //Assert
+ _pricingServiceMock.Verify(p => p.GetSubTotal(It.IsAny(), product, customer, store,
+ currency, true), Times.Once);
+ }
+
[TestMethod]
public async Task GetShoppingCartSubTotalTest()
{
@@ -106,7 +141,7 @@ public async Task GetShoppingCartSubTotalTest()
.Returns(() => Task.FromResult((IList)new List()));
var shoppingCartItem = new List { new() { Quantity = 1 } };
- var result = await _service.GetShoppingCartSubTotal(shoppingCartItem, false);
+ var result = await _service.GetShoppingCartSubTotal(shoppingCartItem, _context, false);
Assert.IsTrue(result.subTotalWithDiscount >= 0);
}
@@ -122,7 +157,7 @@ public async Task GetShoppingCartAdditionalShippingChargeTest()
var shoppingCartItem = new List
{ new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10 } };
//Act
- var result = await _service.GetShoppingCartAdditionalShippingCharge(shoppingCartItem);
+ var result = await _service.GetShoppingCartAdditionalShippingCharge(shoppingCartItem, _context);
//Assert
Assert.AreEqual(10, result);
}
@@ -139,7 +174,7 @@ public async Task IsFreeShippingTest()
new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10, IsFreeShipping = true }
};
//Act
- var result = await _service.GetShoppingCartAdditionalShippingCharge(shoppingCartItem);
+ var result = await _service.GetShoppingCartAdditionalShippingCharge(shoppingCartItem, _context);
//Assert
Assert.AreEqual(0, result);
}
@@ -155,7 +190,7 @@ public async Task AdjustShippingRateTest()
var shoppingCartItem = new List
{ new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10 } };
//Act
- var result = await _service.AdjustShippingRate(10, shoppingCartItem);
+ var result = await _service.AdjustShippingRate(10, shoppingCartItem, _context);
//Assert
Assert.AreEqual(20, result.shippingRate);
@@ -184,7 +219,7 @@ public async Task GetShoppingCartShippingTotalTest()
new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10, IsFreeShipping = true }
};
//Act
- var result = await _service.GetShoppingCartShippingTotal(shoppingCartItem);
+ var result = await _service.GetShoppingCartShippingTotal(shoppingCartItem, _context);
//Assert
Assert.AreEqual(0, result.shoppingCartShippingTotal);
@@ -214,7 +249,7 @@ public async Task GetTaxTotalTest()
new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10, IsFreeShipping = true }
};
//Act
- var result = await _service.GetTaxTotal(shoppingCartItem, false);
+ var result = await _service.GetTaxTotal(shoppingCartItem, _context, false);
//Assert
Assert.AreEqual(0, result.taxtotal);
}
@@ -243,7 +278,7 @@ public async Task GetShoppingCartTotalTest()
new() { Quantity = 1, IsShipEnabled = true, AdditionalShippingChargeProduct = 10, IsFreeShipping = true }
};
//Act
- var result = await _service.GetShoppingCartTotal(shoppingCartItem, false, false);
+ var result = await _service.GetShoppingCartTotal(shoppingCartItem, _context, false, false);
//Assert
Assert.AreEqual(0, result.shoppingCartTotal);
}
@@ -259,6 +294,20 @@ public async Task ConvertLoyaltyPointsToAmountTest()
Assert.AreEqual(10, result);
}
+ [TestMethod]
+ public async Task ConvertLoyaltyPointsToAmount_RoundsInThePrimaryStoreCurrency()
+ {
+ //Arrange
+ _loyaltyPointsSettings.ExchangeRate = 0.33;
+ _shoppingCartSettings.RoundPrices = true;
+ _currencyServiceMock.Setup(x => x.GetPrimaryStoreCurrency())
+ .ReturnsAsync(new Currency { CurrencyCode = "USD", NumberDecimal = 2 });
+ //Act
+ var result = await _service.ConvertLoyaltyPointsToAmount(15);
+ //Assert
+ Assert.AreEqual(4.95, result, 0.0001);
+ }
+
[TestMethod]
public void ConvertAmountToLoyaltyPointsTest()
{
diff --git a/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs b/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs
new file mode 100644
index 0000000000..08816ea40c
--- /dev/null
+++ b/src/Tests/Grand.Business.Messages.Tests/Commands/GetOrderTokensCommandHandlerTests.cs
@@ -0,0 +1,65 @@
+using Grand.Business.Core.Commands.Messages.Tokens;
+using Grand.Business.Core.Interfaces.Catalog.Prices;
+using Grand.Business.Core.Interfaces.Catalog.Products;
+using Grand.Business.Core.Interfaces.Checkout.GiftVouchers;
+using Grand.Business.Core.Interfaces.Checkout.Payments;
+using Grand.Business.Core.Interfaces.Common.Addresses;
+using Grand.Business.Core.Interfaces.Common.Directory;
+using Grand.Business.Core.Interfaces.Common.Localization;
+using Grand.Business.Core.Interfaces.Customers;
+using Grand.Business.Messages.Commands.Handlers.Messages;
+using Grand.Domain.Customers;
+using Grand.Domain.Directory;
+using Grand.Domain.Localization;
+using Grand.Domain.Orders;
+using Grand.Domain.Stores;
+using Grand.Domain.Tax;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Grand.Business.Messages.Tests.Commands;
+
+[TestClass]
+public class GetOrderTokensCommandHandlerTests
+{
+ private readonly Currency _orderCurrency = new() { CurrencyCode = "EUR" };
+ private GetOrderTokensCommandHandler _handler;
+ private Mock _priceFormatterMock;
+
+ [TestInitialize]
+ public void Init()
+ {
+ var languageService = new Mock();
+ languageService.Setup(x => x.GetLanguageById(It.IsAny())).ReturnsAsync(new Language { Id = "1" });
+ var currencyService = new Mock();
+ currencyService.Setup(x => x.GetCurrencyByCode("EUR")).ReturnsAsync(_orderCurrency);
+ var giftVoucherService = new Mock();
+ giftVoucherService.Setup(x => x.GetAllGiftVoucherUsageHistory(It.IsAny()))
+ .ReturnsAsync(new List());
+ _priceFormatterMock = new Mock();
+ _priceFormatterMock.Setup(x => x.FormatTaxRate(It.IsAny())).Returns("23");
+ _priceFormatterMock.Setup(x => x.FormatPrice(It.IsAny(), It.IsAny()))
+ .Returns((double price, Currency currency) => $"{currency?.CurrencyCode}{price}");
+
+ _handler = new GetOrderTokensCommandHandler(languageService.Object, currencyService.Object,
+ new Mock().Object, new Mock().Object, _priceFormatterMock.Object,
+ new Mock().Object, new Mock().Object,
+ new Mock().Object, giftVoucherService.Object, new TaxSettings());
+ }
+
+ [TestMethod]
+ public async Task Handle_FormatsTaxRatesInTheOrderCurrency()
+ {
+ //Arrange
+ var order = new Order { CustomerCurrencyCode = "EUR", CustomerLanguageId = "1", OrderTax = 10 };
+ order.OrderTaxes.Add(new OrderTax { Percent = 23, Amount = 10 });
+
+ //Act
+ var result = await _handler.Handle(new GetOrderTokensCommand {
+ Order = order, Customer = new Customer(), Store = new Store(), Host = new DomainHost { Url = "https://localhost/" }
+ }, CancellationToken.None);
+
+ //Assert
+ Assert.AreEqual("EUR10", result.TaxRates["23"]);
+ }
+}
diff --git a/src/Tests/Grand.Modules.Tests/Plugins/Payments/BrainTreePaymentProviderTests.cs b/src/Tests/Grand.Modules.Tests/Plugins/Payments/BrainTreePaymentProviderTests.cs
index f3ea2c7ba2..be98bd1fdc 100644
--- a/src/Tests/Grand.Modules.Tests/Plugins/Payments/BrainTreePaymentProviderTests.cs
+++ b/src/Tests/Grand.Modules.Tests/Plugins/Payments/BrainTreePaymentProviderTests.cs
@@ -32,6 +32,7 @@ public void Init()
_currencyService = new Mock();
var contextAccessor = new Mock();
contextAccessor.Setup(c => c.WorkContext.WorkingCurrency).Returns(new Currency());
+ contextAccessor.Setup(c => c.StoreContext.CurrentStore).Returns(new Grand.Domain.Stores.Store());
var services = new ServiceCollection();
services.AddSingleton(_settings);
@@ -61,7 +62,7 @@ public async Task GetAdditionalHandlingFee_Percentage_WorksWithoutHttpContext()
_settings.AdditionalFee = 2;
_settings.AdditionalFeePercentage = true;
var cart = new List { new() };
- _subTotalService.Setup(s => s.GetShoppingCartSubTotal(cart, true))
+ _subTotalService.Setup(s => s.GetShoppingCartSubTotal(cart, It.IsAny(), true))
.ReturnsAsync((0d, new List(), 500d, 500d, new SortedDictionary()));
var provider = _scope.ServiceProvider.GetRequiredService();
diff --git a/src/Tests/Grand.Modules.Tests/Plugins/Payments/CashOnDeliveryPaymentProviderTests.cs b/src/Tests/Grand.Modules.Tests/Plugins/Payments/CashOnDeliveryPaymentProviderTests.cs
index 7d2b57ca78..8edd9bca68 100644
--- a/src/Tests/Grand.Modules.Tests/Plugins/Payments/CashOnDeliveryPaymentProviderTests.cs
+++ b/src/Tests/Grand.Modules.Tests/Plugins/Payments/CashOnDeliveryPaymentProviderTests.cs
@@ -35,6 +35,7 @@ public void Init()
_paymentTransactionService = new Mock();
var contextAccessor = new Mock();
contextAccessor.Setup(c => c.WorkContext.WorkingCurrency).Returns(_workingCurrency);
+ contextAccessor.Setup(c => c.StoreContext.CurrentStore).Returns(new Grand.Domain.Stores.Store());
//no IHttpContextAccessor: the provider has to work outside a request
var services = new ServiceCollection();
@@ -71,7 +72,7 @@ public async Task GetAdditionalHandlingFee_Percentage_IsShareOfSubtotalWithoutSe
_settings.AdditionalFee = 10;
_settings.AdditionalFeePercentage = true;
var cart = new List { new() };
- _subTotalService.Setup(s => s.GetShoppingCartSubTotal(cart, true))
+ _subTotalService.Setup(s => s.GetShoppingCartSubTotal(cart, It.IsAny(), true))
.ReturnsAsync((0d, new List(), 200d, 180d, new SortedDictionary()));
var fee = await Provider.GetAdditionalHandlingFee(cart);
@@ -91,7 +92,8 @@ public async Task GetAdditionalHandlingFee_FixedValue_IsConvertedToWorkingCurren
var fee = await Provider.GetAdditionalHandlingFee(new List());
Assert.AreEqual(20, fee);
- _subTotalService.Verify(s => s.GetShoppingCartSubTotal(It.IsAny>(), It.IsAny()),
+ _subTotalService.Verify(s => s.GetShoppingCartSubTotal(It.IsAny>(), It.IsAny(),
+ It.IsAny()),
Times.Never);
}
diff --git a/src/Tests/Grand.Web.Common.Tests/Extensions/CalculationContextExtensionsTests.cs b/src/Tests/Grand.Web.Common.Tests/Extensions/CalculationContextExtensionsTests.cs
new file mode 100644
index 0000000000..8720a69837
--- /dev/null
+++ b/src/Tests/Grand.Web.Common.Tests/Extensions/CalculationContextExtensionsTests.cs
@@ -0,0 +1,37 @@
+using Grand.Domain.Customers;
+using Grand.Domain.Directory;
+using Grand.Domain.Stores;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
+using Grand.Web.Common.Extensions;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Grand.Web.Common.Tests.Extensions;
+
+[TestClass]
+public class CalculationContextExtensionsTests
+{
+ [TestMethod]
+ public void CalculationContext_TakesCustomerStoreCurrencyAndTaxDisplayOfTheRequest()
+ {
+ //Arrange
+ var customer = new Customer();
+ var store = new Store { Id = "s1" };
+ var currency = new Currency { CurrencyCode = "EUR" };
+ var accessor = new Mock();
+ accessor.Setup(a => a.WorkContext.CurrentCustomer).Returns(customer);
+ accessor.Setup(a => a.WorkContext.WorkingCurrency).Returns(currency);
+ accessor.Setup(a => a.WorkContext.TaxDisplayType).Returns(TaxDisplayType.IncludingTax);
+ accessor.Setup(a => a.StoreContext.CurrentStore).Returns(store);
+
+ //Act
+ var context = accessor.Object.CalculationContext();
+
+ //Assert
+ Assert.AreSame(customer, context.Customer);
+ Assert.AreSame(store, context.Store);
+ Assert.AreSame(currency, context.Currency);
+ Assert.IsTrue(context.IncludingTax);
+ }
+}
diff --git a/src/Tests/Grand.Web.Tests/Features/Handlers/Products/GetProductStructuredDataHandlerTests.cs b/src/Tests/Grand.Web.Tests/Features/Handlers/Products/GetProductStructuredDataHandlerTests.cs
index f743d10397..d01c6c63d7 100644
--- a/src/Tests/Grand.Web.Tests/Features/Handlers/Products/GetProductStructuredDataHandlerTests.cs
+++ b/src/Tests/Grand.Web.Tests/Features/Handlers/Products/GetProductStructuredDataHandlerTests.cs
@@ -6,6 +6,8 @@
using Grand.Domain.Directory;
using Grand.Domain.Orders;
using Grand.Domain.Shipping;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
using Grand.Web.Features.Handlers.Products;
using Grand.Web.Features.Models.Products;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -44,13 +46,15 @@ public void Init()
_pricingServiceMock = new Mock();
_taxServiceMock = new Mock();
//shipping taxed at 10%, product prices passed through
- _taxServiceMock.Setup(x => x.GetShippingPrice(It.IsAny(), _customer, _store))
- .ReturnsAsync((double price, Customer _, Domain.Stores.Store _) => (price * 1.1, 10));
+ _taxServiceMock.Setup(x => x.GetShippingPrice(It.IsAny(), false, _customer, _store))
+ .ReturnsAsync((double price, bool _, Customer _, Domain.Stores.Store _) => (price * 1.1, 10));
_taxServiceMock.Setup(x => x.GetProductPrice(It.IsAny(), It.IsAny(), It.IsAny(),
_customer, _store))
.ReturnsAsync((Product _, double price, bool _, Customer _, Domain.Stores.Store _) => (price, 0));
+ var contextAccessor = new Mock();
+ contextAccessor.Setup(c => c.WorkContext.TaxDisplayType).Returns(TaxDisplayType.ExcludingTax);
_handler = new GetProductStructuredDataHandler(_countryServiceMock.Object, _currencyServiceMock.Object,
- _pricingServiceMock.Object, _taxServiceMock.Object, _orderSettings, _shippingSettings);
+ _pricingServiceMock.Object, _taxServiceMock.Object, _orderSettings, _shippingSettings, contextAccessor.Object);
}
private Task Handle(Product product) =>
diff --git a/src/Web/Grand.Web.Admin/Controllers/ShoppingCartController.cs b/src/Web/Grand.Web.Admin/Controllers/ShoppingCartController.cs
index 2c63fcb0c4..573f7193e7 100644
--- a/src/Web/Grand.Web.Admin/Controllers/ShoppingCartController.cs
+++ b/src/Web/Grand.Web.Admin/Controllers/ShoppingCartController.cs
@@ -7,6 +7,8 @@
using Grand.Business.Core.Interfaces.Customers;
using Grand.Domain.Permissions;
using Grand.Domain.Orders;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
using Grand.Web.AdminShared.Models.ShoppingCart;
using Grand.Web.Common.DataSource;
using Grand.Web.Common.Security.Authorization;
@@ -27,7 +29,8 @@ public ShoppingCartController(ICustomerService customerService,
IPricingService priceCalculationService,
ITranslationService translationService,
IProductAttributeFormatter productAttributeFormatter,
- IProductService productService)
+ IProductService productService,
+ IContextAccessor contextAccessor)
{
_customerService = customerService;
_dateTimeService = dateTimeService;
@@ -38,6 +41,7 @@ public ShoppingCartController(ICustomerService customerService,
_translationService = translationService;
_productAttributeFormatter = productAttributeFormatter;
_productService = productService;
+ _contextAccessor = contextAccessor;
}
#endregion
@@ -53,6 +57,7 @@ public ShoppingCartController(ICustomerService customerService,
private readonly ITranslationService _translationService;
private readonly IProductAttributeFormatter _productAttributeFormatter;
private readonly IProductService _productService;
+ private readonly IContextAccessor _contextAccessor;
#endregion
@@ -99,6 +104,7 @@ public async Task GetCartDetails(string customerId)
foreach (var sci in cart)
{
var store = await _storeService.GetStoreById(sci.StoreId);
+ var priceStore = store ?? _contextAccessor.StoreContext.CurrentStore;
var product = await _productService.GetProductById(sci.ProductId);
var sciModel = new ShoppingCartItemModel {
Id = sci.Id,
@@ -108,9 +114,19 @@ public async Task GetCartDetails(string customerId)
ProductName = product.Name,
AttributeInfo = await _productAttributeFormatter.FormatAttributes(product, sci.Attributes, customer),
UnitPrice = _priceFormatter.FormatPrice((await _taxService.GetProductPrice(product,
- (await _pricingService.GetUnitPrice(sci, product)).unitprice)).productprice),
+ (await _pricingService.GetUnitPrice(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).unitprice,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice,
+ _contextAccessor.WorkContext.WorkingCurrency),
Total = _priceFormatter.FormatPrice((await _taxService.GetProductPrice(product,
- (await _pricingService.GetSubTotal(sci, product)).subTotal)).productprice),
+ (await _pricingService.GetSubTotal(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).subTotal,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice,
+ _contextAccessor.WorkContext.WorkingCurrency),
UpdatedOn = sci.UpdatedOnUtc.HasValue
? _dateTimeService.ConvertToUserTime(sci.UpdatedOnUtc.Value, DateTimeKind.Utc)
: _dateTimeService.ConvertToUserTime(sci.CreatedOnUtc, DateTimeKind.Utc)
@@ -160,6 +176,7 @@ public async Task GetWishlistDetails(string customerId)
foreach (var sci in cart)
{
var store = await _storeService.GetStoreById(sci.StoreId);
+ var priceStore = store ?? _contextAccessor.StoreContext.CurrentStore;
var product = await _productService.GetProductById(sci.ProductId);
var sciModel = new ShoppingCartItemModel {
Id = sci.Id,
@@ -169,9 +186,19 @@ public async Task GetWishlistDetails(string customerId)
ProductName = product.Name,
AttributeInfo = await _productAttributeFormatter.FormatAttributes(product, sci.Attributes, customer),
UnitPrice = _priceFormatter.FormatPrice((await _taxService.GetProductPrice(product,
- (await _pricingService.GetUnitPrice(sci, product)).unitprice)).productprice),
+ (await _pricingService.GetUnitPrice(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).unitprice,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice,
+ _contextAccessor.WorkContext.WorkingCurrency),
Total = _priceFormatter.FormatPrice((await _taxService.GetProductPrice(product,
- (await _pricingService.GetSubTotal(sci, product)).subTotal)).productprice),
+ (await _pricingService.GetSubTotal(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).subTotal,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice,
+ _contextAccessor.WorkContext.WorkingCurrency),
UpdatedOn = sci.UpdatedOnUtc.HasValue
? _dateTimeService.ConvertToUserTime(sci.UpdatedOnUtc.Value, DateTimeKind.Utc)
: _dateTimeService.ConvertToUserTime(sci.CreatedOnUtc, DateTimeKind.Utc)
diff --git a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
index 6f39b69a61..4aab4bf264 100644
--- a/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
+++ b/src/Web/Grand.Web.AdminShared/Services/CustomerViewModelService.cs
@@ -858,22 +858,32 @@ public virtual async Task> PrepareShoppingCartItemM
foreach (var sci in cart)
{
var store = await _storeService.GetStoreById(sci.StoreId);
+ var priceStore = store ?? _contextAccessor.StoreContext.CurrentStore;
var product = await _productService.GetProductById(sci.ProductId);
if (product != null)
{
var price = (await taxService.GetProductPrice(product,
- (await priceCalculationService.GetUnitPrice(sci, product)).unitprice)).productprice;
+ (await priceCalculationService.GetUnitPrice(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).unitprice,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice;
var sciModel = new ShoppingCartItemModel {
Id = sci.Id,
Store = store != null ? store.Shortcut : "Unknown",
ProductId = sci.ProductId,
Quantity = sci.Quantity,
ProductName = product.Name,
- AttributeInfo = await productAttributeFormatter.FormatAttributes(product, sci.Attributes),
- UnitPrice = priceFormatter.FormatPrice(price),
+ AttributeInfo = await productAttributeFormatter.FormatAttributes(product, sci.Attributes, customer),
+ UnitPrice = priceFormatter.FormatPrice(price, _contextAccessor.WorkContext.WorkingCurrency),
UnitPriceValue = price,
Total = priceFormatter.FormatPrice((await taxService.GetProductPrice(product,
- (await priceCalculationService.GetSubTotal(sci, product)).subTotal)).productprice),
+ (await priceCalculationService.GetSubTotal(sci, product,
+ customer, priceStore,
+ _contextAccessor.WorkContext.WorkingCurrency)).subTotal,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ customer, priceStore)).productprice,
+ _contextAccessor.WorkContext.WorkingCurrency),
UpdatedOn = sci.UpdatedOnUtc.HasValue
? _dateTimeService.ConvertToUserTime(sci.UpdatedOnUtc.Value, DateTimeKind.Utc)
: _dateTimeService.ConvertToUserTime(sci.CreatedOnUtc, DateTimeKind.Utc)
diff --git a/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs
index 351deae882..9d9bf62e10 100644
--- a/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs
+++ b/src/Web/Grand.Web.AdminShared/Services/MerchandiseReturnViewModelService.cs
@@ -58,7 +58,7 @@ public virtual async Task PrepareMerchandiseReturnModel(
unitPriceInclTaxInCustomerCurrency += orderItem.UnitPriceInclTax * item.Quantity;
}
- model.Total = priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency);
+ model.Total = priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, contextAccessor.WorkContext.WorkingCurrency);
model.Quantity = merchandiseReturn.MerchandiseReturnItems.Sum(x => x.Quantity);
model.Id = merchandiseReturn.Id;
model.OrderId = order.Id;
@@ -230,7 +230,7 @@ public virtual async Task produ
bidsModel.Add(new ProductModel.BidModel {
BidId = x.Id,
ProductId = x.ProductId,
- Amount = priceFormatter.FormatPrice(x.Amount),
+ Amount = priceFormatter.FormatPrice(x.Amount, contextAccessor.WorkContext.WorkingCurrency),
Date = dateTimeService.ConvertToUserTime(x.Date, DateTimeKind.Utc),
CustomerId = x.CustomerId,
Email = (await customerService.GetCustomerById(x.CustomerId))?.Email,
diff --git a/src/Web/Grand.Web.Common/Extensions/CalculationContextExtensions.cs b/src/Web/Grand.Web.Common/Extensions/CalculationContextExtensions.cs
new file mode 100644
index 0000000000..bad1405815
--- /dev/null
+++ b/src/Web/Grand.Web.Common/Extensions/CalculationContextExtensions.cs
@@ -0,0 +1,20 @@
+using Grand.Business.Core.Utilities.Checkout;
+using Grand.Infrastructure;
+
+namespace Grand.Web.Common.Extensions;
+
+///
+/// Builds the calculation context of the current request
+///
+public static class CalculationContextExtensions
+{
+ ///
+ /// Gets the calculation context of the current request: its customer, store, working currency and tax display
+ ///
+ public static CalculationContext CalculationContext(this IContextAccessor contextAccessor)
+ {
+ return new CalculationContext(contextAccessor.WorkContext.CurrentCustomer,
+ contextAccessor.StoreContext.CurrentStore, contextAccessor.WorkContext.WorkingCurrency,
+ contextAccessor.WorkContext.TaxDisplayType);
+ }
+}
diff --git a/src/Web/Grand.Web/Controllers/CheckoutController.cs b/src/Web/Grand.Web/Controllers/CheckoutController.cs
index 971499d6c7..3ba659f5bd 100644
--- a/src/Web/Grand.Web/Controllers/CheckoutController.cs
+++ b/src/Web/Grand.Web/Controllers/CheckoutController.cs
@@ -938,7 +938,8 @@ private async Task LoadStepAfterShippingMethod(IList CustomerMerchandiseReturns()
var model = await _mediator.Send(new GetMerchandiseReturns {
Customer = _contextAccessor.WorkContext.CurrentCustomer,
Store = _contextAccessor.StoreContext.CurrentStore,
- Language = _contextAccessor.WorkContext.WorkingLanguage
+ Language = _contextAccessor.WorkContext.WorkingLanguage,
+ Currency = _contextAccessor.WorkContext.WorkingCurrency
});
return View(model);
diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetFormatBasePriceHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetFormatBasePriceHandler.cs
index 255e386ab1..49f0580509 100644
--- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetFormatBasePriceHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetFormatBasePriceHandler.cs
@@ -49,7 +49,7 @@ public async Task Handle(GetFormatBasePrice request, CancellationToken c
//do not round. otherwise, it can cause issues
await _measureService.ConvertWeight(productAmount, productUnit, referenceUnit, false) *
referenceAmount;
- var basePriceStr = _priceFormatter.FormatPrice(basePrice);
+ var basePriceStr = _priceFormatter.FormatPrice(basePrice, request.Currency);
var result = string.Format(_translationService.GetResource("Products.BasePrice"),
basePriceStr, referenceAmount.ToString("G29"), referenceUnit.Name);
diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchAutoCompleteHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchAutoCompleteHandler.cs
index a58f1b49cf..d003a5b403 100644
--- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchAutoCompleteHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchAutoCompleteHandler.cs
@@ -13,6 +13,7 @@
using Grand.Domain.Catalog;
using Grand.Domain.Common;
using Grand.Domain.Media;
+using Grand.Domain.Tax;
using Grand.Infrastructure;
using Grand.Infrastructure.Configuration;
using Grand.Web.Features.Models.Catalog;
@@ -114,7 +115,8 @@ public async Task> Handle(GetSearchAutoComplete r
var storeurl = _contextAccessor.StoreContext.CurrentHost.Url.TrimEnd('/');
var displayPrices =
- await _permissionService.Authorize(StandardPermission.DisplayPrices, _contextAccessor.WorkContext.CurrentCustomer);
+ await _permissionService.Authorize(StandardPermission.DisplayPrices,
+ _contextAccessor.WorkContext.CurrentCustomer);
foreach (var item in products)
{
@@ -163,7 +165,8 @@ public async Task> Handle(GetSearchAutoComplete r
if (brand is not { Published: true }) continue;
var allow = true;
- if (!_accessControlConfig.IgnoreAcl && !_aclService.Authorize(brand, _contextAccessor.WorkContext.CurrentCustomer))
+ if (!_accessControlConfig.IgnoreAcl && !_aclService.Authorize(brand,
+ _contextAccessor.WorkContext.CurrentCustomer))
allow = false;
if (!_accessControlConfig.IgnoreStoreLimitations && !_aclService.Authorize(brand, storeId))
@@ -187,7 +190,8 @@ public async Task> Handle(GetSearchAutoComplete r
var category = await _categoryService.GetCategoryById(item);
if (category is not { Published: true }) continue;
var allow = true;
- if (!_accessControlConfig.IgnoreAcl && !_aclService.Authorize(category, _contextAccessor.WorkContext.CurrentCustomer))
+ if (!_accessControlConfig.IgnoreAcl && !_aclService.Authorize(category,
+ _contextAccessor.WorkContext.CurrentCustomer))
allow = false;
if (!_accessControlConfig.IgnoreStoreLimitations && !_aclService.Authorize(category, storeId))
allow = false;
@@ -245,14 +249,18 @@ public async Task> Handle(GetSearchAutoComplete r
var finalPriceWithoutDiscount =
(await _taxService.GetProductPrice(product,
(await _pricingService.GetFinalPrice(product, request.Customer, request.Store, request.Currency,
- includeDiscounts: false)).finalPrice)).productprice;
+ includeDiscounts: false)).finalPrice,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice;
var appliedPrice = await _pricingService.GetFinalPrice(product, request.Customer, request.Store,
request.Currency, includeDiscounts: true);
- var finalPriceWithDiscount = (await _taxService.GetProductPrice(product, appliedPrice.finalPrice)).productprice;
+ var finalPriceWithDiscount = (await _taxService.GetProductPrice(product, appliedPrice.finalPrice,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax,
+ _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice;
- var price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount);
- var priceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount);
+ var price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount, request.Currency);
+ var priceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount, request.Currency);
return (price, priceWithDiscount);
}
diff --git a/src/Web/Grand.Web/Features/Handlers/Checkout/GetIsPaymentWorkflowRequiredHandler.cs b/src/Web/Grand.Web/Features/Handlers/Checkout/GetIsPaymentWorkflowRequiredHandler.cs
index 45a14863a0..ece3c146ef 100644
--- a/src/Web/Grand.Web/Features/Handlers/Checkout/GetIsPaymentWorkflowRequiredHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Checkout/GetIsPaymentWorkflowRequiredHandler.cs
@@ -1,5 +1,7 @@
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Domain.Payments;
+using Grand.Infrastructure;
+using Grand.Web.Common.Extensions;
using Grand.Web.Features.Models.Checkout;
using Grand.Mediator;
@@ -7,12 +9,15 @@ namespace Grand.Web.Features.Handlers.Checkout;
public class GetIsPaymentWorkflowRequiredHandler : IRequestHandler
{
+ private readonly IContextAccessor _contextAccessor;
private readonly IOrderCalculationService _orderTotalCalculationService;
private readonly PaymentSettings _paymentSettings;
public GetIsPaymentWorkflowRequiredHandler(IOrderCalculationService orderTotalCalculationService,
- PaymentSettings paymentSettings)
+ PaymentSettings paymentSettings,
+ IContextAccessor contextAccessor)
{
+ _contextAccessor = contextAccessor;
_orderTotalCalculationService = orderTotalCalculationService;
_paymentSettings = paymentSettings;
}
@@ -22,7 +27,7 @@ public async Task Handle(GetIsPaymentWorkflowRequired request, Cancellatio
var result = true;
//check whether order total equals zero
var shoppingCartTotalBase =
- (await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart, request.UseLoyaltyPoints))
+ (await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart, _contextAccessor.CalculationContext(), request.UseLoyaltyPoints))
.shoppingCartTotal;
if (shoppingCartTotalBase is 0 && !_paymentSettings.ShowPaymentIfCartIsZero)
result = false;
diff --git a/src/Web/Grand.Web/Features/Handlers/Checkout/GetPaymentMethodHandler.cs b/src/Web/Grand.Web/Features/Handlers/Checkout/GetPaymentMethodHandler.cs
index 37ae97c59b..f83c241bf4 100644
--- a/src/Web/Grand.Web/Features/Handlers/Checkout/GetPaymentMethodHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Checkout/GetPaymentMethodHandler.cs
@@ -4,10 +4,12 @@
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Checkout.Payments;
using Grand.Business.Core.Interfaces.Common.Directory;
+using Grand.Business.Core.Utilities.Checkout;
using Grand.Domain.Common;
using Grand.Domain.Customers;
using Grand.Domain.Orders;
using Grand.Domain.Payments;
+using Grand.Domain.Tax;
using Grand.Web.Features.Models.Checkout;
using Grand.Web.Models.Checkout;
using Grand.Mediator;
@@ -64,7 +66,9 @@ await _orderTotalCalculationService.ConvertLoyaltyPointsToAmount(loyaltyPointsBa
model.LoyaltyPointsAmount = _priceFormatter.FormatPrice(loyaltyPointsAmount, request.Currency);
model.LoyaltyPointsBalance = loyaltyPointsBalance;
var shoppingCartTotalBase =
- (await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart, true)).shoppingCartTotal;
+ (await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart,
+ new CalculationContext(request.Customer, request.Store, request.Currency,
+ request.TaxDisplayType), true)).shoppingCartTotal;
model.LoyaltyPointsEnoughToPayForOrder = shoppingCartTotalBase is 0;
}
}
@@ -89,7 +93,8 @@ await _orderTotalCalculationService.ConvertLoyaltyPointsToAmount(loyaltyPointsBa
//payment method additional fee
var paymentMethodAdditionalFee =
await _paymentService.GetAdditionalHandlingFee(request.Cart, pm.SystemName);
- var rate = (await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, request.Customer, request.Store))
+ var rate = (await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee,
+ request.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store))
.paymentPrice;
if (rate > 0)
pmModel.Fee = _priceFormatter.FormatPrice(rate, request.Currency);
diff --git a/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingAddressHandler.cs b/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingAddressHandler.cs
index 9f7bb660a8..fa3a98f1c4 100644
--- a/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingAddressHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingAddressHandler.cs
@@ -6,6 +6,8 @@
using Grand.Business.Core.Interfaces.Common.Security;
using Grand.Domain.Common;
using Grand.Domain.Shipping;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
using Grand.Web.Features.Models.Checkout;
using Grand.Web.Features.Models.Common;
using Grand.Web.Models.Checkout;
@@ -15,6 +17,7 @@ namespace Grand.Web.Features.Handlers.Checkout;
public class GetShippingAddressHandler : IRequestHandler
{
+ private readonly IContextAccessor _contextAccessor;
private readonly IAclService _aclService;
private readonly AddressSettings _addressSettings;
private readonly ICountryService _countryService;
@@ -38,8 +41,10 @@ public GetShippingAddressHandler(
IAclService aclService,
IMediator mediator,
ShippingSettings shippingSettings,
- AddressSettings addressSettings)
+ AddressSettings addressSettings,
+ IContextAccessor contextAccessor)
{
+ _contextAccessor = contextAccessor;
_shippingService = shippingService;
_pickupPointService = pickupPointService;
_taxService = taxService;
@@ -101,7 +106,8 @@ private async Task PreparePickupPoints(CheckoutShippingAddressModel model, GetSh
};
if (pickupPoint.PickupFee > 0)
{
- var amount = (await _taxService.GetShippingPrice(pickupPoint.PickupFee, request.Customer, request.Store))
+ var amount = (await _taxService.GetShippingPrice(pickupPoint.PickupFee,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store))
.shippingPrice;
amount = await _currencyService.ConvertFromPrimaryStoreCurrency(amount, request.Currency);
pickupPointModel.PickupFee = _priceFormatter.FormatPrice(amount, request.Currency);
diff --git a/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingMethodHandler.cs b/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingMethodHandler.cs
index c890deeae2..83d2b0e79b 100644
--- a/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingMethodHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Checkout/GetShippingMethodHandler.cs
@@ -6,6 +6,9 @@
using Grand.Domain.Common;
using Grand.Domain.Customers;
using Grand.Domain.Shipping;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
+using Grand.Web.Common.Extensions;
using Grand.Web.Features.Models.Checkout;
using Grand.Web.Models.Checkout;
using Grand.Mediator;
@@ -14,6 +17,7 @@ namespace Grand.Web.Features.Handlers.Checkout;
public class GetShippingMethodHandler : IRequestHandler
{
+ private readonly IContextAccessor _contextAccessor;
private readonly IOrderCalculationService _orderTotalCalculationService;
private readonly IPriceFormatter _priceFormatter;
private readonly IShippingService _shippingService;
@@ -24,8 +28,10 @@ public GetShippingMethodHandler(IShippingService shippingService,
ICustomerService customerService,
IOrderCalculationService orderTotalCalculationService,
ITaxService taxService,
- IPriceFormatter priceFormatter)
+ IPriceFormatter priceFormatter,
+ IContextAccessor contextAccessor)
{
+ _contextAccessor = contextAccessor;
_shippingService = shippingService;
_customerService = customerService;
_orderTotalCalculationService = orderTotalCalculationService;
@@ -62,10 +68,11 @@ await _customerService.UpdateUserField(request.Customer,
//adjust rate
var shippingTotal = (await _orderTotalCalculationService.AdjustShippingRate(
- shippingOption.Rate, request.Cart)).shippingRate;
+ shippingOption.Rate, request.Cart, _contextAccessor.CalculationContext())).shippingRate;
- var rateBase = (await _taxService.GetShippingPrice(shippingTotal, request.Customer, request.Store)).shippingPrice;
- soModel.Fee = _priceFormatter.FormatPrice(rateBase);
+ var rateBase = (await _taxService.GetShippingPrice(shippingTotal,
+ _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store)).shippingPrice;
+ soModel.Fee = _priceFormatter.FormatPrice(rateBase, request.Currency);
model.ShippingMethods.Add(soModel);
}
diff --git a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnDetailsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnDetailsHandler.cs
index 9847684149..7892df35c0 100644
--- a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnDetailsHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnDetailsHandler.cs
@@ -77,7 +77,7 @@ public async Task Handle(GetMerchandiseReturnDeta
orderItem.UnitPriceInclTax
:
//excluding tax
- orderItem.UnitPriceExclTax);
+ orderItem.UnitPriceExclTax, _contextAccessor.WorkContext.WorkingCurrency);
model.MerchandiseReturnItems.Add(new MerchandiseReturnDetailsModel.MerchandiseReturnItemModel {
OrderItemId = item.OrderItemId,
diff --git a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnsHandler.cs
index 1c2fc7941d..59d8d5897a 100644
--- a/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnsHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Orders/GetMerchandiseReturnsHandler.cs
@@ -73,7 +73,7 @@ public async Task Handle(GetMerchandiseReturns
MerchandiseReturnStatus = _enumTranslationService.GetTranslationEnum(merchandiseReturn.MerchandiseReturnStatus),
CreatedOn = _dateTimeService.ConvertToUserTime(merchandiseReturn.CreatedOnUtc, DateTimeKind.Utc),
ProductsCount = merchandiseReturn.MerchandiseReturnItems.Sum(x => x.Quantity),
- ReturnTotal = _priceFormatter.FormatPrice(total)
+ ReturnTotal = _priceFormatter.FormatPrice(total, request.Currency)
};
model.Items.Add(itemModel);
diff --git a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsAttributeChangeHandler.cs b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsAttributeChangeHandler.cs
index e5cc106dfa..7b1886c7ec 100644
--- a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsAttributeChangeHandler.cs
+++ b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsAttributeChangeHandler.cs
@@ -9,6 +9,8 @@
using Grand.Domain.Common;
using Grand.Domain.Media;
using Grand.Domain.Orders;
+using Grand.Domain.Tax;
+using Grand.Infrastructure;
using Grand.Web.Extensions;
using Grand.Web.Features.Models.Products;
using Grand.Web.Features.Models.ShoppingCart;
@@ -21,6 +23,7 @@ namespace Grand.Web.Features.Handlers.Products;
public class GetProductDetailsAttributeChangeHandler : IRequestHandler