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 { + private readonly IContextAccessor _contextAccessor; private readonly MediaSettings _mediaSettings; private readonly IMediator _mediator; private readonly IOutOfStockSubscriptionService _outOfStockSubscriptionService; @@ -44,8 +47,10 @@ public GetProductDetailsAttributeChangeHandler( ITranslationService translationService, IPictureService pictureService, ShoppingCartSettings shoppingCartSettings, - MediaSettings mediaSettings) + MediaSettings mediaSettings, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _mediator = mediator; _stockQuantityService = stockQuantityService; _permissionService = permissionService; @@ -99,9 +104,11 @@ await _mediator.Send( true); var finalPrice = unitprice.unitprice; - var productprice = await _taxService.GetProductPrice(request.Product, finalPrice); + var productprice = await _taxService.GetProductPrice(request.Product, finalPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); var finalPriceWithDiscount = productprice.productprice; - model.Price = _priceFormatter.FormatPrice(finalPriceWithDiscount); + model.Price = _priceFormatter.FormatPrice(finalPriceWithDiscount, request.Currency); } //stock diff --git a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs index 7414d62688..fbd145b0c2 100644 --- a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs @@ -19,6 +19,7 @@ using Grand.Domain.Orders; using Grand.Domain.Permissions; using Grand.Domain.Seo; +using Grand.Domain.Tax; using Grand.Domain.Vendors; using Grand.Infrastructure; using Grand.Infrastructure.Caching; @@ -455,8 +456,10 @@ private async Task PrepareStandardProperties(Product produc //additional shipping charge if (model.AdditionalShippingCharge > 0) model.AdditionalShippingChargeStr = _priceFormatter.FormatPrice( - (await _taxService.GetShippingPrice(model.AdditionalShippingCharge, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)) - .shippingPrice); + (await _taxService.GetShippingPrice(model.AdditionalShippingCharge, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)) + .shippingPrice, _contextAccessor.WorkContext.WorkingCurrency); //ask question us on the product if (model.AskQuestionOnProduct) @@ -679,25 +682,35 @@ await _pictureService.GetPictureUrl(defaultPicture.PictureId, 0, !isAssociatedPr { if (!product.CallForPrice) { - var oldproductprice = await _taxService.GetProductPrice(product, product.OldPrice); + var oldproductprice = await _taxService.GetProductPrice(product, product.OldPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); var oldPriceBase = oldproductprice.productprice; var finalPriceWithoutDiscount = (await _taxService.GetProductPrice(product, (await _pricingService.GetFinalPrice(product, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, - _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: false)).finalPrice)).productprice; - - var appliedPrice = await _pricingService.GetFinalPrice(product, _contextAccessor.WorkContext.CurrentCustomer, - _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: true); + _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: false)).finalPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; + + var appliedPrice = await _pricingService.GetFinalPrice(product, + _contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: true); var finalPriceWithDiscount = - (await _taxService.GetProductPrice(product, appliedPrice.finalPrice)).productprice; + (await _taxService.GetProductPrice(product, appliedPrice.finalPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; var oldPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, _contextAccessor.WorkContext.WorkingCurrency); if (finalPriceWithoutDiscount != oldPrice && oldPrice > 0) - model.OldPrice = _priceFormatter.FormatPrice(oldPrice); + model.OldPrice = _priceFormatter.FormatPrice(oldPrice, + _contextAccessor.WorkContext.WorkingCurrency); - model.Price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount); + model.Price = _priceFormatter.FormatPrice(finalPriceWithoutDiscount, + _contextAccessor.WorkContext.WorkingCurrency); if (appliedPrice.appliedDiscounts.Any()) model.AppliedDiscounts = appliedPrice.appliedDiscounts; if (appliedPrice.preferredTierPrice != null) @@ -708,11 +721,13 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(oldPriceBase, var catalogPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(product.CatalogPrice, _contextAccessor.WorkContext.WorkingCurrency); - model.CatalogPrice = _priceFormatter.FormatPrice(catalogPrice); + model.CatalogPrice = _priceFormatter.FormatPrice(catalogPrice, + _contextAccessor.WorkContext.WorkingCurrency); } if (finalPriceWithoutDiscount != finalPriceWithDiscount) - model.PriceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount); + model.PriceWithDiscount = _priceFormatter.FormatPrice(finalPriceWithDiscount, + _contextAccessor.WorkContext.WorkingCurrency); model.PriceValue = finalPriceWithDiscount; @@ -730,7 +745,8 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(product.CatalogPrice, if (product.ProductTypeId == ProductType.Reservation) { model.IsReservation = true; - var priceStr = _priceFormatter.FormatPrice(finalPriceWithDiscount); + var priceStr = _priceFormatter.FormatPrice(finalPriceWithDiscount, + _contextAccessor.WorkContext.WorkingCurrency); model.ReservationPrice = string.Format(_translationService.GetResource(product.ResourceReservationProductPeriod()), priceStr, product.Interval); @@ -742,13 +758,15 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(product.CatalogPrice, var highestBid = await _currencyService.ConvertFromPrimaryStoreCurrency(product.HighestBid, _contextAccessor.WorkContext.WorkingCurrency); - model.HighestBid = _priceFormatter.FormatPrice(highestBid); + model.HighestBid = _priceFormatter.FormatPrice(highestBid, + _contextAccessor.WorkContext.WorkingCurrency); model.HighestBidValue = highestBid; model.DisableBuyButton = product.DisableBuyButton; var startPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(product.StartPrice, _contextAccessor.WorkContext.WorkingCurrency); - model.StartPrice = _priceFormatter.FormatPrice(startPrice); + model.StartPrice = _priceFormatter.FormatPrice(startPrice, + _contextAccessor.WorkContext.WorkingCurrency); model.StartPriceValue = startPrice; } } @@ -831,8 +849,8 @@ await _currencyService.ConvertFromPrimaryStoreCurrency(product.MaxEnteredPrice, : minimumCustomerEnteredPrice; model.CustomerEnteredPriceRange = string.Format( _translationService.GetResource("Products.EnterProductPrice.Range"), - _priceFormatter.FormatPrice(minimumCustomerEnteredPrice), - _priceFormatter.FormatPrice(maximumCustomerEnteredPrice)); + _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, _contextAccessor.WorkContext.WorkingCurrency), + _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, _contextAccessor.WorkContext.WorkingCurrency)); return model; } @@ -940,9 +958,13 @@ private ProductDetailsModel.GiftVoucherModel PrepareGiftVoucherModel(Product pro if (displayPrices) { var attributeValuePriceAdjustment = - await _pricingService.GetProductAttributeValuePriceAdjustment(attributeValue); + await _pricingService.GetProductAttributeValuePriceAdjustment(attributeValue, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency); var productprice = - await _taxService.GetProductPrice(product, attributeValuePriceAdjustment); + await _taxService.GetProductPrice(product, attributeValuePriceAdjustment, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); switch (productprice.productprice) { case > 0: @@ -1083,10 +1105,14 @@ await _pictureService.GetPictureUrl(attributeValue.ImageSquaresPictureId), { var tier = new ProductDetailsModel.TierPriceModel(); var priceBase = await _taxService.GetProductPrice(product, (await _pricingService.GetFinalPrice(product, - _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, _contextAccessor.WorkContext.WorkingCurrency, - 0, _catalogSettings.DisplayTierPricesWithDiscounts, tierPrice.Quantity)).finalPrice); + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency, + 0, _catalogSettings.DisplayTierPricesWithDiscounts, tierPrice.Quantity)).finalPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); tier.Quantity = tierPrice.Quantity; - tier.Price = _priceFormatter.FormatPrice(priceBase.productprice, _contextAccessor.WorkContext.WorkingCurrency); + tier.Price = _priceFormatter.FormatPrice(priceBase.productprice, + _contextAccessor.WorkContext.WorkingCurrency); model.Add(tier); } @@ -1162,9 +1188,13 @@ private async Task PrepareProductReservation(ProductDetailsModel model, Product if (displayPrices) { var productprice = await _taxService.GetProductPrice(p1, - (await _pricingService.GetFinalPrice(p1, _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, - _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: true)).finalPrice); - bundleProduct.Price = _priceFormatter.FormatPrice(productprice.productprice); + (await _pricingService.GetFinalPrice(p1, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency, includeDiscounts: true)).finalPrice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); + bundleProduct.Price = _priceFormatter.FormatPrice(productprice.productprice, + _contextAccessor.WorkContext.WorkingCurrency); bundleProduct.PriceValue = productprice.productprice; } diff --git a/src/Web/Grand.Web/Features/Handlers/Products/GetProductStructuredDataHandler.cs b/src/Web/Grand.Web/Features/Handlers/Products/GetProductStructuredDataHandler.cs index bec3cb3d69..73f3271e66 100644 --- a/src/Web/Grand.Web/Features/Handlers/Products/GetProductStructuredDataHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Products/GetProductStructuredDataHandler.cs @@ -4,6 +4,8 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Domain.Orders; using Grand.Domain.Shipping; +using Grand.Domain.Tax; +using Grand.Infrastructure; using Grand.Web.Features.Models.Products; using Grand.Web.Models.Catalog; using Grand.Mediator; @@ -12,6 +14,7 @@ namespace Grand.Web.Features.Handlers.Products; public class GetProductStructuredDataHandler : IRequestHandler { + private readonly IContextAccessor _contextAccessor; private readonly ICountryService _countryService; private readonly ICurrencyService _currencyService; private readonly OrderSettings _orderSettings; @@ -25,8 +28,10 @@ public GetProductStructuredDataHandler( IPricingService pricingService, ITaxService taxService, OrderSettings orderSettings, - ShippingSettings shippingSettings) + ShippingSettings shippingSettings, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _countryService = countryService; _currencyService = currencyService; _pricingService = pricingService; @@ -99,7 +104,8 @@ public async Task Handle(GetProductStructuredData re //convert theirs, plus the product's own surcharge, taxed as shipping var rate = await _currencyService.ConvertFromPrimaryStoreCurrency( _shippingSettings.DefaultShippingRate.Value + product.AdditionalShippingCharge, request.Currency); - rate = (await _taxService.GetShippingPrice(rate, request.Customer, request.Store)).shippingPrice; + rate = (await _taxService.GetShippingPrice(rate, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store)).shippingPrice; return RoundingHelper.RoundPrice(rate, request.Currency); } diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetAddToCartHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetAddToCartHandler.cs index 0a1c5afaa5..72395fb36c 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetAddToCartHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetAddToCartHandler.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Catalog; using Grand.Domain.Media; using Grand.Domain.Orders; @@ -111,13 +112,16 @@ await _productAttributeFormatter.FormatAttributes(request.Product, request.Attri else { var productprices = await _taxService.GetProductPrice(request.Product, - (await _pricingService.GetUnitPrice(request.ShoppingCartItem, request.Product)).unitprice); + (await _pricingService.GetUnitPrice(request.ShoppingCartItem, request.Product, request.Customer, request.Store, + request.Currency)).unitprice, + request.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store); model.Price = !request.CustomerEnteredPrice.HasValue - ? _priceFormatter.FormatPrice(productprices.productprice) - : _priceFormatter.FormatPrice(request.CustomerEnteredPrice.Value); + ? _priceFormatter.FormatPrice(productprices.productprice, request.Currency) + : _priceFormatter.FormatPrice(request.CustomerEnteredPrice.Value, request.Currency); model.DecimalPrice = request.CustomerEnteredPrice ?? productprices.productprice; model.TotalPrice = - _priceFormatter.FormatPrice(productprices.productprice * request.ShoppingCartItem.Quantity); + _priceFormatter.FormatPrice(productprices.productprice * request.ShoppingCartItem.Quantity, + request.Currency); } //picture @@ -153,7 +157,9 @@ await _productAttributeFormatter.FormatAttributes(request.Product, request.Attri var subTotalIncludingTax = request.TaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal; var shoppingCartSubTotal = - await _orderTotalCalculationService.GetShoppingCartSubTotal(cart, subTotalIncludingTax); + await _orderTotalCalculationService.GetShoppingCartSubTotal(cart, + new CalculationContext(request.Customer, request.Store, request.Currency, + request.TaxDisplayType), subTotalIncludingTax); model.SubTotalIncludingTax = subTotalIncludingTax; model.SubTotal = _priceFormatter.FormatPrice(shoppingCartSubTotal.subTotalWithoutDiscount, request.Currency); @@ -167,7 +173,7 @@ await _productAttributeFormatter.FormatAttributes(request.Product, request.Attri case ShoppingCartType.Auctions: model.IsAuction = true; model.HighestBidValue = request.Product.HighestBid; - model.HighestBid = _priceFormatter.FormatPrice(request.Product.HighestBid); + model.HighestBid = _priceFormatter.FormatPrice(request.Product.HighestBid, request.Currency); model.EndTime = request.Product.AvailableEndDateTimeUtc; break; } diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetEstimateShippingResultHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetEstimateShippingResultHandler.cs index 7d6d78446b..142d90cc20 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetEstimateShippingResultHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetEstimateShippingResultHandler.cs @@ -6,6 +6,9 @@ using Grand.Domain.Common; using Grand.Domain.Orders; using Grand.Domain.Shipping; +using Grand.Domain.Tax; +using Grand.Infrastructure; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.ShoppingCart; using Grand.Web.Models.ShoppingCart; using Grand.Mediator; @@ -14,6 +17,7 @@ namespace Grand.Web.Features.Handlers.ShoppingCart; public class GetEstimateShippingResultHandler : IRequestHandler { + private readonly IContextAccessor _contextAccessor; private readonly IOrderCalculationService _orderTotalCalculationService; private readonly IPickupPointService _pickupPointService; private readonly IPriceFormatter _priceFormatter; @@ -30,8 +34,10 @@ public GetEstimateShippingResultHandler( ITaxService taxService, ITranslationService translationService, IPriceFormatter priceFormatter, - ShippingSettings shippingSettings) + ShippingSettings shippingSettings, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _shippingService = shippingService; _pickupPointService = pickupPointService; _orderTotalCalculationService = orderTotalCalculationService; @@ -73,10 +79,11 @@ public async Task Handle(GetEstimateShippingResult //calculate discounted and taxed rate var total = await _orderTotalCalculationService.AdjustShippingRate(shippingOption.Rate, - request.Cart); + request.Cart, _contextAccessor.CalculationContext()); var shippingTotal = total.shippingRate; - var rate = (await _taxService.GetShippingPrice(shippingTotal, request.Customer, request.Store)).shippingPrice; + var rate = (await _taxService.GetShippingPrice(shippingTotal, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store)).shippingPrice; soModel.Price = _priceFormatter.FormatPrice(rate, request.Currency); model.ShippingOptions.Add(soModel); } @@ -92,7 +99,8 @@ public async Task Handle(GetEstimateShippingResult }; var shippingTotal = pickupPoints.Max(x => x.PickupFee); - var rate = (await _taxService.GetShippingPrice(shippingTotal, request.Customer, request.Store)).shippingPrice; + var rate = (await _taxService.GetShippingPrice(shippingTotal, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store)).shippingPrice; soModel.Price = _priceFormatter.FormatPrice(rate, request.Currency); model.ShippingOptions.Add(soModel); } diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniShoppingCartHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniShoppingCartHandler.cs index 58b959a18b..99431262da 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniShoppingCartHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniShoppingCartHandler.cs @@ -8,6 +8,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Checkout; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Media; @@ -106,11 +107,14 @@ public async Task Handle(GetMiniShoppingCart request, model.TotalProducts = cart.Sum(x => x.Quantity); if (!cart.Any()) return model; + var context = new CalculationContext(request.Customer, request.Store, request.Currency, + request.TaxDisplayType); + //subtotal var subTotalIncludingTax = request.TaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal; var shoppingCartSubTotal = - await _orderTotalCalculationService.GetShoppingCartSubTotal(cart, subTotalIncludingTax); + await _orderTotalCalculationService.GetShoppingCartSubTotal(cart, context, subTotalIncludingTax); model.SubTotalIncludingTax = subTotalIncludingTax; model.SubTotal = _priceFormatter.FormatPrice(shoppingCartSubTotal.subTotalWithoutDiscount, request.Currency); @@ -121,7 +125,7 @@ public async Task Handle(GetMiniShoppingCart request, .Any(); var minOrderSubtotalAmountOk = await _mediator.Send(new ValidateMinShoppingCartSubtotalAmountCommand { - Customer = request.Customer, + Context = context, Cart = cart.Where (x => x.ShoppingCartTypeId is ShoppingCartType.ShoppingCart or ShoppingCartType.Auctions).ToList() }, cancellationToken); @@ -188,8 +192,10 @@ public async Task Handle(GetMiniShoppingCart request, else { var productprices = await _taxService.GetProductPrice(product, - (await _pricingService.GetUnitPrice(sci, product)).unitprice); - cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprices.productprice); + (await _pricingService.GetUnitPrice(sci, product, request.Customer, request.Store, + request.Currency)).unitprice, + request.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store); + cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprices.productprice, request.Currency); } //picture diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniWishlistHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniWishlistHandler.cs index 1d14f97d02..39f4fb03ed 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniWishlistHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetMiniWishlistHandler.cs @@ -8,6 +8,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.ShoppingCart; using Grand.Web.Models.Media; @@ -18,6 +20,7 @@ namespace Grand.Web.Features.Handlers.ShoppingCart; public class GetMiniWishlistHandler : IRequestHandler { + private readonly IContextAccessor _contextAccessor; private readonly LinkGenerator _linkGenerator; private readonly MediaSettings _mediaSettings; private readonly IPictureService _pictureService; @@ -39,8 +42,10 @@ public GetMiniWishlistHandler( IPictureService pictureService, LinkGenerator linkGenerator, ShoppingCartSettings shoppingCartSettings, - MediaSettings mediaSettings) + MediaSettings mediaSettings, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _productService = productService; _productAttributeFormatter = productAttributeFormatter; _translationService = translationService; @@ -92,9 +97,13 @@ public async Task Handle(GetMiniWishlist request, Cancellatio else { var productprice = await _taxService.GetProductPrice(product, - (await _pricingService.GetUnitPrice(sci, product)).unitprice); + (await _pricingService.GetUnitPrice(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency)).unitprice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); - cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprice.productprice); + cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprice.productprice, request.Currency); cartItemModel.UnitPriceValue = productprice.productprice; cartItemModel.TaxRate = productprice.taxRate; } diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetOrderTotalsHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetOrderTotalsHandler.cs index 077054a8e5..5b820989c5 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetOrderTotalsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetOrderTotalsHandler.cs @@ -56,29 +56,32 @@ public async Task Handle(GetOrderTotals request, CancellationT if (!request.Cart.Any()) return model; + var context = new CalculationContext(request.Customer, request.Store, request.Currency, + request.TaxDisplayType); + //subtotal - await PrepareSubtotal(model, request); + await PrepareSubtotal(model, request, context); //shipping info - await PrepareShippingInfo(model, request); + await PrepareShippingInfo(model, request, context); //payment method fee await PreparePayment(model, request); //tax - await PrepareTax(model, request); + await PrepareTax(model, request, context); //total - await PrepareTotal(model, request); + await PrepareTotal(model, request, context); return model; } - private async Task PrepareSubtotal(OrderTotalsModel model, GetOrderTotals request) + private async Task PrepareSubtotal(OrderTotalsModel model, GetOrderTotals request, CalculationContext context) { var subTotalIncludingTax = request.TaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal; var shoppingCartSubTotal = - await _orderTotalCalculationService.GetShoppingCartSubTotal(request.Cart, subTotalIncludingTax); + await _orderTotalCalculationService.GetShoppingCartSubTotal(request.Cart, context, subTotalIncludingTax); model.SubTotalIncludingTax = subTotalIncludingTax; model.SubTotal = _priceFormatter.FormatPrice(shoppingCartSubTotal.subTotalWithoutDiscount, request.Currency); model.SubTotalValue = shoppingCartSubTotal.subTotalWithoutDiscount; @@ -91,13 +94,14 @@ private async Task PrepareSubtotal(OrderTotalsModel model, GetOrderTotals reques } } - private async Task PrepareShippingInfo(OrderTotalsModel model, GetOrderTotals request) + private async Task PrepareShippingInfo(OrderTotalsModel model, GetOrderTotals request, + CalculationContext context) { model.RequiresShipping = request.Cart.RequiresShipping(); if (model.RequiresShipping) { var shoppingCartShippingBase = - (await _orderTotalCalculationService.GetShoppingCartShippingTotal(request.Cart)) + (await _orderTotalCalculationService.GetShoppingCartShippingTotal(request.Cart, context)) .shoppingCartShippingTotal; if (shoppingCartShippingBase.HasValue) { @@ -120,7 +124,8 @@ private async Task PreparePayment(OrderTotalsModel model, GetOrderTotals request var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFee(request.Cart, paymentMethodSystemName); var paymentMethodAdditionalFeeWithTaxBase = - (await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, request.Customer, request.Store)) + (await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, + request.TaxDisplayType == TaxDisplayType.IncludingTax, request.Customer, request.Store)) .paymentPrice; if (paymentMethodAdditionalFeeWithTaxBase > 0) { @@ -130,7 +135,7 @@ private async Task PreparePayment(OrderTotalsModel model, GetOrderTotals request } } - private async Task PrepareTax(OrderTotalsModel model, GetOrderTotals request) + private async Task PrepareTax(OrderTotalsModel model, GetOrderTotals request, CalculationContext context) { bool displayTax; bool displayTaxRates; @@ -141,7 +146,7 @@ private async Task PrepareTax(OrderTotalsModel model, GetOrderTotals request) } else { - var taxtotal = await _orderTotalCalculationService.GetTaxTotal(request.Cart); + var taxtotal = await _orderTotalCalculationService.GetTaxTotal(request.Cart, context); var taxRates = taxtotal.taxRates; if (taxtotal.taxtotal == 0 && _taxSettings.HideZeroTax) @@ -167,9 +172,9 @@ private async Task PrepareTax(OrderTotalsModel model, GetOrderTotals request) model.DisplayTax = displayTax; } - private async Task PrepareTotal(OrderTotalsModel model, GetOrderTotals request) + private async Task PrepareTotal(OrderTotalsModel model, GetOrderTotals request, CalculationContext context) { - var carttotal = await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart); + var carttotal = await _orderTotalCalculationService.GetShoppingCartTotal(request.Cart, context); var shoppingCartTotalBase = carttotal.shoppingCartTotal; var orderTotalDiscountAmountBase = carttotal.discountAmount; var appliedGiftVouchers = carttotal.appliedGiftVouchers; @@ -208,7 +213,7 @@ private async Task PrepareTotal(OrderTotalsModel model, GetOrderTotals request) shoppingCartTotalBase.HasValue) { var shippingBaseInclTax = model.RequiresShipping - ? (await _orderTotalCalculationService.GetShoppingCartShippingTotal(request.Cart, true)) + ? (await _orderTotalCalculationService.GetShoppingCartShippingTotal(request.Cart, context, true)) .shoppingCartShippingTotal : 0; var earnLoyaltyPoints = shoppingCartTotalBase.Value - shippingBaseInclTax ?? 0; diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs index d8b6582dba..37f3c6f485 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetShoppingCartHandler.cs @@ -19,7 +19,10 @@ using Grand.Domain.Customers; using Grand.Domain.Media; using Grand.Domain.Orders; +using Grand.Domain.Tax; using Grand.Infrastructure.Configuration; +using Grand.Infrastructure; +using Grand.Web.Common.Extensions; using Grand.Web.Common.Localization; using Grand.Web.Extensions; using Grand.Web.Features.Models.ShoppingCart; @@ -32,6 +35,7 @@ namespace Grand.Web.Features.Handlers.ShoppingCart; public class GetShoppingCartHandler : IRequestHandler { + private readonly IContextAccessor _contextAccessor; private readonly CatalogSettings _catalogSettings; private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; private readonly ICheckoutAttributeParser _checkoutAttributeParser; @@ -90,8 +94,10 @@ public GetShoppingCartHandler( ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings, CommonSettings commonSettings, - IEnumTranslationService enumTranslationService) + IEnumTranslationService enumTranslationService, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _productService = productService; _pictureService = pictureService; _translationService = translationService; @@ -163,7 +169,7 @@ private async Task PrepareSimpleProperties(ShoppingCartModel model, GetShoppingC else { var minOrderSubtotalAmountOk = await _mediator.Send(new ValidateMinShoppingCartSubtotalAmountCommand { - Customer = request.Customer, + Context = _contextAccessor.CalculationContext(), Cart = request.Cart.Where(x => x.ShoppingCartTypeId is ShoppingCartType.ShoppingCart or ShoppingCartType.Auctions).ToList() }); @@ -244,17 +250,21 @@ await _checkoutAttributeService.GetAllCheckoutAttributes(request.Store.Id, //display price if allowed if (!await _permissionService.Authorize(StandardPermission.DisplayPrices)) continue; - var priceAdjustmentBase = (await _taxService.GetCheckoutAttributePrice(attribute, attributeValue)) + var priceAdjustmentBase = (await _taxService.GetCheckoutAttributePrice(attribute, attributeValue, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)) .checkoutPrice; var priceAdjustment = await _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, request.Currency); switch (priceAdjustmentBase) { case > 0: - attributeValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment); + attributeValueModel.PriceAdjustment = "+" + _priceFormatter.FormatPrice(priceAdjustment, + request.Currency); break; case < 0: - attributeValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment); + attributeValueModel.PriceAdjustment = "-" + _priceFormatter.FormatPrice(-priceAdjustment, + request.Currency); break; } } @@ -440,18 +450,26 @@ private async Task PrepareCartItems(ShoppingCartModel model, GetShoppingCart req } else { - var unitprices = await _pricingService.GetUnitPrice(sci, product); + var unitprices = await _pricingService.GetUnitPrice(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency); var appliedDiscounts = unitprices.appliedDiscounts; - var productprices = await _taxService.GetProductPrice(product, unitprices.unitprice); + var productprices = await _taxService.GetProductPrice(product, unitprices.unitprice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); cartItemModel.UnitPriceWithoutDiscountValue = (await _taxService.GetProductPrice(product, - (await _pricingService.GetUnitPrice(sci, product, false)).unitprice)).productprice; + (await _pricingService.GetUnitPrice(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency, false)).unitprice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; cartItemModel.UnitPriceWithoutDiscount = - _priceFormatter.FormatPrice(cartItemModel.UnitPriceWithoutDiscountValue); + _priceFormatter.FormatPrice(cartItemModel.UnitPriceWithoutDiscountValue, request.Currency); cartItemModel.UnitPriceValue = productprices.productprice; - cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprices.productprice); + cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprices.productprice, request.Currency); if (appliedDiscounts.Any()) { if (appliedDiscounts.Any(x => x.MaximumDiscountedQuantity.HasValue)) @@ -462,20 +480,28 @@ private async Task PrepareCartItems(ShoppingCartModel model, GetShoppingCart req } //sub total - var subtotal = await _pricingService.GetSubTotal(sci, product); + var subtotal = await _pricingService.GetSubTotal(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency); var shoppingCartItemDiscountBase = subtotal.discountAmount; var shoppingCartItemSubTotalWithDiscount = - (await _taxService.GetProductPrice(product, subtotal.subTotal)).productprice; - cartItemModel.SubTotal = _priceFormatter.FormatPrice(shoppingCartItemSubTotalWithDiscount); + (await _taxService.GetProductPrice(product, subtotal.subTotal, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; + cartItemModel.SubTotal = _priceFormatter.FormatPrice(shoppingCartItemSubTotalWithDiscount, + request.Currency); cartItemModel.SubTotalValue = shoppingCartItemSubTotalWithDiscount; //display an applied discount amount if (shoppingCartItemDiscountBase > 0) { shoppingCartItemDiscountBase = - (await _taxService.GetProductPrice(product, shoppingCartItemDiscountBase)).productprice; + (await _taxService.GetProductPrice(product, shoppingCartItemDiscountBase, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; if (shoppingCartItemDiscountBase > 0) - cartItemModel.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscountBase); + cartItemModel.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscountBase, + request.Currency); } } diff --git a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs index ebb9de49f5..f6f43be4cb 100644 --- a/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/ShoppingCart/GetWishlistHandler.cs @@ -13,6 +13,8 @@ using Grand.Domain.Customers; using Grand.Domain.Media; using Grand.Domain.Orders; +using Grand.Domain.Tax; +using Grand.Infrastructure; using Grand.Web.Common.Localization; using Grand.Web.Extensions; using Grand.Web.Features.Models.ShoppingCart; @@ -25,6 +27,7 @@ namespace Grand.Web.Features.Handlers.ShoppingCart; public class GetWishlistHandler : IRequestHandler { + private readonly IContextAccessor _contextAccessor; private readonly CatalogSettings _catalogSettings; private readonly LinkGenerator _linkGenerator; private readonly MediaSettings _mediaSettings; @@ -53,8 +56,10 @@ public GetWishlistHandler( ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings, MediaSettings mediaSettings, - IEnumTranslationService enumTranslationService) + IEnumTranslationService enumTranslationService, + IContextAccessor contextAccessor) { + _contextAccessor = contextAccessor; _permissionService = permissionService; _productService = productService; _productAttributeFormatter = productAttributeFormatter; @@ -142,8 +147,12 @@ public async Task Handle(GetWishlist request, CancellationToken c else { var productprice = await _taxService.GetProductPrice(product, - (await _pricingService.GetUnitPrice(sci, product)).unitprice); - cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprice.productprice); + (await _pricingService.GetUnitPrice(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency)).unitprice, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); + cartItemModel.UnitPrice = _priceFormatter.FormatPrice(productprice.productprice, request.Currency); } //subtotal, discount @@ -154,18 +163,25 @@ public async Task Handle(GetWishlist request, CancellationToken c else { //sub total - var subtotal = await _pricingService.GetSubTotal(sci, product); + var subtotal = await _pricingService.GetSubTotal(sci, product, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore, + _contextAccessor.WorkContext.WorkingCurrency); var shoppingCartItemDiscountBase = subtotal.discountAmount; - var productprices = await _taxService.GetProductPrice(product, subtotal.subTotal); - cartItemModel.SubTotal = _priceFormatter.FormatPrice(productprices.productprice); + var productprices = await _taxService.GetProductPrice(product, subtotal.subTotal, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore); + cartItemModel.SubTotal = _priceFormatter.FormatPrice(productprices.productprice, request.Currency); //display an applied discount amount if (shoppingCartItemDiscountBase > 0) { shoppingCartItemDiscountBase = - (await _taxService.GetProductPrice(product, shoppingCartItemDiscountBase)).productprice; + (await _taxService.GetProductPrice(product, shoppingCartItemDiscountBase, + _contextAccessor.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore)).productprice; if (shoppingCartItemDiscountBase > 0) - cartItemModel.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscountBase); + cartItemModel.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscountBase, + request.Currency); } } diff --git a/src/Web/Grand.Web/Features/Models/Checkout/GetPaymentMethod.cs b/src/Web/Grand.Web/Features/Models/Checkout/GetPaymentMethod.cs index 0db29fdae4..b5d1fcda9c 100644 --- a/src/Web/Grand.Web/Features/Models/Checkout/GetPaymentMethod.cs +++ b/src/Web/Grand.Web/Features/Models/Checkout/GetPaymentMethod.cs @@ -3,6 +3,7 @@ using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Domain.Stores; +using Grand.Domain.Tax; using Grand.Web.Models.Checkout; using Grand.Mediator; @@ -13,6 +14,7 @@ public class GetPaymentMethod : IRequest public Customer Customer { get; set; } public Domain.Stores.Store Store { get; set; } public Currency Currency { get; set; } + public TaxDisplayType TaxDisplayType { get; set; } public Language Language { get; set; } public IList Cart { get; set; } public string FilterByCountryId { get; set; } diff --git a/src/Web/Grand.Web/Features/Models/Orders/GetMerchandiseReturns.cs b/src/Web/Grand.Web/Features/Models/Orders/GetMerchandiseReturns.cs index 4860b1f9f7..76d7251041 100644 --- a/src/Web/Grand.Web/Features/Models/Orders/GetMerchandiseReturns.cs +++ b/src/Web/Grand.Web/Features/Models/Orders/GetMerchandiseReturns.cs @@ -1,4 +1,5 @@ using Grand.Domain.Customers; +using Grand.Domain.Directory; using Grand.Domain.Localization; using Grand.Domain.Stores; using Grand.Web.Models.Orders; @@ -11,4 +12,5 @@ public class GetMerchandiseReturns : IRequest public Customer Customer { get; set; } public Domain.Stores.Store Store { get; set; } public Language Language { get; set; } + public Currency Currency { get; set; } } \ No newline at end of file