Try your search with a different keyword or use * as a wildcard.
using System.Globalization;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Services.Attributes;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Discounts;
using Nop.Services.Payments;
using Nop.Services.Shipping;
using Nop.Services.Tax;
namespace Nop.Services.Orders;
///
/// Order service
///
public partial class OrderTotalCalculationService : IOrderTotalCalculationService
{
#region Fields
protected readonly CatalogSettings _catalogSettings;
protected readonly IAddressService _addressService;
protected readonly IAttributeParser _checkoutAttributeParser;
protected readonly ICustomerService _customerService;
protected readonly IDiscountService _discountService;
protected readonly IGenericAttributeService _genericAttributeService;
protected readonly IGiftCardService _giftCardService;
protected readonly IOrderService _orderService;
protected readonly IPaymentService _paymentService;
protected readonly IPriceCalculationService _priceCalculationService;
protected readonly IProductService _productService;
protected readonly IRewardPointService _rewardPointService;
protected readonly IShippingPluginManager _shippingPluginManager;
protected readonly IShippingService _shippingService;
protected readonly IShoppingCartService _shoppingCartService;
protected readonly IStoreContext _storeContext;
protected readonly ITaxService _taxService;
protected readonly IWorkContext _workContext;
protected readonly RewardPointsSettings _rewardPointsSettings;
protected readonly ShippingSettings _shippingSettings;
protected readonly ShoppingCartSettings _shoppingCartSettings;
protected readonly TaxSettings _taxSettings;
#endregion
#region Ctor
public OrderTotalCalculationService(CatalogSettings catalogSettings,
IAddressService addressService,
IAttributeParser checkoutAttributeParser,
ICustomerService customerService,
IDiscountService discountService,
IGenericAttributeService genericAttributeService,
IGiftCardService giftCardService,
IOrderService orderService,
IPaymentService paymentService,
IPriceCalculationService priceCalculationService,
IProductService productService,
IRewardPointService rewardPointService,
IShippingPluginManager shippingPluginManager,
IShippingService shippingService,
IShoppingCartService shoppingCartService,
IStoreContext storeContext,
ITaxService taxService,
IWorkContext workContext,
RewardPointsSettings rewardPointsSettings,
ShippingSettings shippingSettings,
ShoppingCartSettings shoppingCartSettings,
TaxSettings taxSettings)
{
_catalogSettings = catalogSettings;
_addressService = addressService;
_checkoutAttributeParser = checkoutAttributeParser;
_customerService = customerService;
_discountService = discountService;
_genericAttributeService = genericAttributeService;
_giftCardService = giftCardService;
_orderService = orderService;
_paymentService = paymentService;
_priceCalculationService = priceCalculationService;
_productService = productService;
_rewardPointService = rewardPointService;
_shippingPluginManager = shippingPluginManager;
_shippingService = shippingService;
_shoppingCartService = shoppingCartService;
_storeContext = storeContext;
_taxService = taxService;
_workContext = workContext;
_rewardPointsSettings = rewardPointsSettings;
_shippingSettings = shippingSettings;
_shoppingCartSettings = shoppingCartSettings;
_taxSettings = taxSettings;
}
#endregion
#region Utilities
///
/// Gets an order discount (applied to order subtotal)
///
/// Customer
/// Order subtotal
///
/// A task that represents the asynchronous operation
/// The task result contains the order discount, Applied discounts
///
protected virtual async Task<(decimal orderDiscount, List appliedDiscounts)> GetOrderSubtotalDiscountAsync(Customer customer,
decimal orderSubTotal)
{
var discountAmount = decimal.Zero;
if (_catalogSettings.IgnoreDiscounts)
return (discountAmount, new List());
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToOrderSubTotal);
var allowedDiscounts = new List();
if (allDiscounts?.Any() == true)
{
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
foreach (var discount in allDiscounts)
{
if (!_discountService.ContainsDiscount(allowedDiscounts, discount) &&
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
{
allowedDiscounts.Add(discount);
}
}
}
var appliedDiscounts = _discountService.GetPreferredDiscount(allowedDiscounts, orderSubTotal, out discountAmount);
if (discountAmount < decimal.Zero)
discountAmount = decimal.Zero;
return (discountAmount, appliedDiscounts);
}
///
/// Gets a shipping discount
///
/// Customer
/// Shipping total
///
/// A task that represents the asynchronous operation
/// The task result contains the shipping discount. Applied discounts
///
protected virtual async Task<(decimal shippingDiscount, List appliedDiscounts)> GetShippingDiscountAsync(Customer customer, decimal shippingTotal)
{
var appliedDiscounts = new List();
var shippingDiscountAmount = decimal.Zero;
if (_catalogSettings.IgnoreDiscounts)
return (shippingDiscountAmount, appliedDiscounts);
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToShipping);
var allowedDiscounts = new List();
if (allDiscounts?.Any() == true)
{
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
foreach (var discount in allDiscounts)
if (!_discountService.ContainsDiscount(allowedDiscounts, discount) &&
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
allowedDiscounts.Add(discount);
}
appliedDiscounts = _discountService.GetPreferredDiscount(allowedDiscounts, shippingTotal, out shippingDiscountAmount);
if (shippingDiscountAmount < decimal.Zero)
shippingDiscountAmount = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
shippingDiscountAmount = await _priceCalculationService.RoundPriceAsync(shippingDiscountAmount);
return (shippingDiscountAmount, appliedDiscounts);
}
///
/// Gets an order discount (applied to order total)
///
/// Customer
/// Order total
///
/// A task that represents the asynchronous operation
/// The task result contains the order discount. Applied discounts
///
protected virtual async Task<(decimal orderDiscount, List appliedDiscounts)> GetOrderTotalDiscountAsync(Customer customer, decimal orderTotal)
{
var discountAmount = decimal.Zero;
if (_catalogSettings.IgnoreDiscounts)
return (discountAmount, new List());
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToOrderTotal);
var allowedDiscounts = new List();
if (allDiscounts?.Any() == true)
{
var couponCodesToValidate = await _customerService.ParseAppliedDiscountCouponCodesAsync(customer);
foreach (var discount in allDiscounts)
{
if (!_discountService.ContainsDiscount(allowedDiscounts, discount) &&
(await _discountService.ValidateDiscountAsync(discount, customer, couponCodesToValidate)).IsValid)
{
allowedDiscounts.Add(discount);
}
}
}
var appliedDiscounts = _discountService.GetPreferredDiscount(allowedDiscounts, orderTotal, out discountAmount);
if (discountAmount < decimal.Zero)
discountAmount = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
discountAmount = await _priceCalculationService.RoundPriceAsync(discountAmount);
return (discountAmount, appliedDiscounts);
}
///
/// Update order total
///
/// UpdateOrderParameters
/// Subtotal (excl tax)
/// Discount amount (excl tax)
/// Shipping (excl tax)
/// Tax
/// A task that represents the asynchronous operation
protected virtual async Task UpdateTotalAsync(UpdateOrderParameters updateOrderParameters, decimal subTotalExclTax,
decimal discountAmountExclTax, decimal shippingTotalExclTax, decimal taxTotal)
{
var updatedOrder = updateOrderParameters.UpdatedOrder;
var customer = await _customerService.GetCustomerByIdAsync(updatedOrder.CustomerId);
var total = subTotalExclTax - discountAmountExclTax + shippingTotalExclTax + updatedOrder.PaymentMethodAdditionalFeeExclTax + taxTotal;
//get discounts for the order total
var (discountAmountTotal, orderAppliedDiscounts) = await GetOrderTotalDiscountAsync(customer, total);
if (total < discountAmountTotal)
discountAmountTotal = total;
total -= discountAmountTotal;
//applied giftcards
foreach (var giftCard in await _giftCardService.GetAllGiftCardsAsync(usedWithOrderId: updatedOrder.Id))
{
if (total <= decimal.Zero)
continue;
var remainingAmount = (await _giftCardService.GetGiftCardUsageHistoryAsync(giftCard))
.Where(history => history.UsedWithOrderId == updatedOrder.Id).Sum(history => history.UsedValue);
var amountCanBeUsed = total > remainingAmount ? remainingAmount : total;
total -= amountCanBeUsed;
}
//reward points
var rewardPointsOfOrder = await _rewardPointService.GetRewardPointsHistoryEntryByIdAsync(updatedOrder.RedeemedRewardPointsEntryId ?? 0);
if (rewardPointsOfOrder != null)
{
var rewardPoints = -rewardPointsOfOrder.Points;
var rewardPointsAmount = await ConvertRewardPointsToAmountAsync(rewardPoints);
if (total < rewardPointsAmount)
{
rewardPoints = ConvertAmountToRewardPoints(total);
rewardPointsAmount = total;
}
if (total > decimal.Zero)
total -= rewardPointsAmount;
//uncomment here for the return unused reward points if new order total less redeemed reward points amount
//if (rewardPoints < -rewardPointsOfOrder.Points)
// _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, store.Id, "Return unused reward points");
if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount)
{
rewardPointsOfOrder.UsedAmount = rewardPointsAmount;
rewardPointsOfOrder.Points = -rewardPoints;
await _rewardPointService.UpdateRewardPointsHistoryEntryAsync(rewardPointsOfOrder);
}
}
//rounding
if (total < decimal.Zero)
total = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
total = await _priceCalculationService.RoundPriceAsync(total);
updatedOrder.OrderDiscount = discountAmountTotal;
updatedOrder.OrderTotal = total;
foreach (var discount in orderAppliedDiscounts)
if (!_discountService.ContainsDiscount(updateOrderParameters.AppliedDiscounts, discount))
updateOrderParameters.AppliedDiscounts.Add(discount);
}
///
/// Update tax rates
///
/// Subtotal tax rates
/// Shipping (incl tax)
/// Shipping (excl tax)
/// Shipping tax rates
/// Order
///
/// A task that represents the asynchronous operation
/// The task result contains the ax total
///
protected virtual async Task UpdateTaxRatesAsync(SortedDictionary subTotalTaxRates, decimal shippingTotalInclTax,
decimal shippingTotalExclTax, decimal shippingTaxRate, Order updatedOrder)
{
var taxRates = new SortedDictionary();
//order subtotal taxes
var subTotalTax = decimal.Zero;
foreach (var kvp in subTotalTaxRates)
{
subTotalTax += kvp.Value;
if (kvp.Key <= decimal.Zero || kvp.Value <= decimal.Zero)
continue;
if (!taxRates.TryGetValue(kvp.Key, out var value))
taxRates.Add(kvp.Key, kvp.Value);
else
taxRates[kvp.Key] = value + kvp.Value;
}
//shipping taxes
var shippingTax = decimal.Zero;
if (_taxSettings.ShippingIsTaxable)
{
shippingTax = shippingTotalInclTax - shippingTotalExclTax;
if (shippingTax < decimal.Zero)
shippingTax = decimal.Zero;
if (shippingTaxRate > decimal.Zero && shippingTax > decimal.Zero)
{
if (!taxRates.TryGetValue(shippingTaxRate, out var value))
taxRates.Add(shippingTaxRate, shippingTax);
else
taxRates[shippingTaxRate] = value + shippingTax;
}
}
//payment method additional fee tax
var paymentMethodAdditionalFeeTax = decimal.Zero;
if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable)
{
paymentMethodAdditionalFeeTax = updatedOrder.PaymentMethodAdditionalFeeInclTax - updatedOrder.PaymentMethodAdditionalFeeExclTax;
if (paymentMethodAdditionalFeeTax < decimal.Zero)
paymentMethodAdditionalFeeTax = decimal.Zero;
if (updatedOrder.PaymentMethodAdditionalFeeExclTax > decimal.Zero)
{
var paymentTaxRate = Math.Round(100 * paymentMethodAdditionalFeeTax / updatedOrder.PaymentMethodAdditionalFeeExclTax, 3);
if (paymentTaxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
{
if (!taxRates.TryGetValue(paymentTaxRate, out var value))
taxRates.Add(paymentTaxRate, paymentMethodAdditionalFeeTax);
else
taxRates[paymentTaxRate] = value + paymentMethodAdditionalFeeTax;
}
}
}
//add at least one tax rate (0%)
if (!taxRates.Any())
taxRates.Add(decimal.Zero, decimal.Zero);
//summarize taxes
var taxTotal = subTotalTax + shippingTax + paymentMethodAdditionalFeeTax;
if (taxTotal < decimal.Zero)
taxTotal = decimal.Zero;
//round tax
if (_shoppingCartSettings.RoundPricesDuringCalculation)
taxTotal = await _priceCalculationService.RoundPriceAsync(taxTotal);
updatedOrder.OrderTax = taxTotal;
updatedOrder.TaxRates = taxRates.Aggregate(string.Empty, (current, next) =>
$"{current}{next.Key.ToString(CultureInfo.InvariantCulture)}:{next.Value.ToString(CultureInfo.InvariantCulture)}; ");
return taxTotal;
}
///
/// Update shipping
///
/// UpdateOrderParameters
/// Cart
/// Subtotal (incl tax)
/// Subtotal (excl tax)
///
/// A task that represents the asynchronous operation
/// The task result contains the shipping total. Shipping (incl tax). Shipping tax rate
///
protected virtual async Task<(decimal shippingTotal, decimal shippingTotalInclTax, decimal shippingTaxRate)> UpdateShippingAsync(UpdateOrderParameters updateOrderParameters, IList restoredCart,
decimal subTotalInclTax, decimal subTotalExclTax)
{
var shippingTotalExclTax = decimal.Zero;
var shippingTotalInclTax = decimal.Zero;
var shippingTaxRate = decimal.Zero;
var updatedOrder = updateOrderParameters.UpdatedOrder;
var customer = await _customerService.GetCustomerByIdAsync(updatedOrder.CustomerId);
var currentCustomer = await _workContext.GetCurrentCustomerAsync();
var store = await _storeContext.GetCurrentStoreAsync();
if (await _shoppingCartService.ShoppingCartRequiresShippingAsync(restoredCart))
{
if (!await IsFreeShippingAsync(restoredCart, _shippingSettings.FreeShippingOverXIncludingTax ? subTotalInclTax : subTotalExclTax))
{
var shippingTotal = decimal.Zero;
if (!string.IsNullOrEmpty(updatedOrder.ShippingRateComputationMethodSystemName))
{
//in the updated order were shipping items
if (updatedOrder.PickupInStore)
{
//customer chose pickup in store method, try to get chosen pickup point
if (_shippingSettings.AllowPickupInStore)
{
var address = await _addressService.GetAddressByIdAsync(updatedOrder.BillingAddressId);
var pickupPointsResponse = await _shippingService.GetPickupPointsAsync(restoredCart, address,
customer, updatedOrder.ShippingRateComputationMethodSystemName, store.Id);
if (pickupPointsResponse.Success)
{
var selectedPickupPoint =
pickupPointsResponse.PickupPoints.FirstOrDefault(point =>
updatedOrder.ShippingMethod.Contains(point.Name));
if (selectedPickupPoint != null)
shippingTotal = selectedPickupPoint.PickupFee;
else
updateOrderParameters.Warnings.Add(
$"Shipping method {updatedOrder.ShippingMethod} could not be loaded");
}
else
updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors);
}
else
updateOrderParameters.Warnings.Add("Pick up in store is not available");
}
else
{
//customer chose shipping to address, try to get chosen shipping option
var shippingAddress = await _addressService.GetAddressByIdAsync(updatedOrder.ShippingAddressId ?? 0);
var shippingOptionsResponse = await _shippingService.GetShippingOptionsAsync(restoredCart, shippingAddress, customer, updatedOrder.ShippingRateComputationMethodSystemName, store.Id);
if (shippingOptionsResponse.Success)
{
var shippingOption = shippingOptionsResponse.ShippingOptions.FirstOrDefault(option =>
updatedOrder.ShippingMethod.Contains(option.Name));
if (shippingOption != null)
shippingTotal = shippingOption.Rate;
else
updateOrderParameters.Warnings.Add(
$"Shipping method {updatedOrder.ShippingMethod} could not be loaded");
}
else
updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors);
}
}
else
{
//before updating order was without shipping
if (_shippingSettings.AllowPickupInStore)
{
//try to get the cheapest pickup point
var address = await _addressService.GetAddressByIdAsync(updatedOrder.BillingAddressId);
var pickupPointsResponse = await _shippingService.GetPickupPointsAsync(restoredCart, address,
currentCustomer, storeId: store.Id);
if (pickupPointsResponse.Success)
{
updateOrderParameters.PickupPoint = pickupPointsResponse.PickupPoints
.OrderBy(point => point.PickupFee).First();
shippingTotal = updateOrderParameters.PickupPoint.PickupFee;
}
else
updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors);
}
else
updateOrderParameters.Warnings.Add("Pick up in store is not available");
if (updateOrderParameters.PickupPoint == null)
{
//or try to get the cheapest shipping option for the shipping to the customer address
var shippingRateComputationMethods = await _shippingPluginManager.LoadActivePluginsAsync();
if (shippingRateComputationMethods.Any())
{
var customerShippingAddress = await _customerService.GetCustomerShippingAddressAsync(customer);
var shippingOptionsResponse = await _shippingService.GetShippingOptionsAsync(restoredCart, customerShippingAddress, currentCustomer, storeId: store.Id);
if (shippingOptionsResponse.Success)
{
var shippingOption = shippingOptionsResponse.ShippingOptions.OrderBy(option => option.Rate)
.First();
updatedOrder.ShippingRateComputationMethodSystemName =
shippingOption.ShippingRateComputationMethodSystemName;
updatedOrder.ShippingMethod = shippingOption.Name;
var updatedShippingAddress = _addressService.CloneAddress(customerShippingAddress);
await _addressService.InsertAddressAsync(updatedShippingAddress);
updatedOrder.ShippingAddressId = updatedShippingAddress.Id;
shippingTotal = shippingOption.Rate;
}
else
updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors);
}
else
updateOrderParameters.Warnings.Add("Shipping rate computation method could not be loaded");
}
}
//additional shipping charge
shippingTotal += await GetShoppingCartAdditionalShippingChargeAsync(restoredCart);
//shipping discounts
var (shippingDiscount, shippingTotalDiscounts) = await GetShippingDiscountAsync(customer, shippingTotal);
shippingTotal -= shippingDiscount;
if (shippingTotal < decimal.Zero)
shippingTotal = decimal.Zero;
shippingTotalExclTax = (await _taxService.GetShippingPriceAsync(shippingTotal, false, customer)).price;
(shippingTotalInclTax, shippingTaxRate) = await _taxService.GetShippingPriceAsync(shippingTotal, true, customer);
//rounding
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
shippingTotalExclTax = await _priceCalculationService.RoundPriceAsync(shippingTotalExclTax);
shippingTotalInclTax = await _priceCalculationService.RoundPriceAsync(shippingTotalInclTax);
}
//change shipping status
if (updatedOrder.ShippingStatus == ShippingStatus.ShippingNotRequired ||
updatedOrder.ShippingStatus == ShippingStatus.NotYetShipped)
updatedOrder.ShippingStatus = ShippingStatus.NotYetShipped;
else
updatedOrder.ShippingStatus = ShippingStatus.PartiallyShipped;
foreach (var discount in shippingTotalDiscounts)
if (!_discountService.ContainsDiscount(updateOrderParameters.AppliedDiscounts, discount))
updateOrderParameters.AppliedDiscounts.Add(discount);
}
}
else
updatedOrder.ShippingStatus = ShippingStatus.ShippingNotRequired;
updatedOrder.OrderShippingExclTax = shippingTotalExclTax;
updatedOrder.OrderShippingInclTax = shippingTotalInclTax;
return (shippingTotalExclTax, shippingTotalInclTax, shippingTaxRate);
}
///
/// Update order parameters
///
/// UpdateOrderParameters
/// Cart
///
/// A task that represents the asynchronous operation
/// The task result contains the subtotal. Subtotal (incl tax). Subtotal tax rates. Discount amount (excl tax)
///
protected virtual async Task<(decimal subtotal, decimal subTotalInclTax, SortedDictionary subTotalTaxRates, decimal discountAmountExclTax)> UpdateSubTotalAsync(UpdateOrderParameters updateOrderParameters, IList restoredCart)
{
var subTotalExclTax = decimal.Zero;
var subTotalInclTax = decimal.Zero;
var subTotalTaxRates = new SortedDictionary();
var updatedOrder = updateOrderParameters.UpdatedOrder;
var updatedOrderItem = updateOrderParameters.UpdatedOrderItem;
foreach (var shoppingCartItem in restoredCart)
{
decimal itemSubTotalExclTax;
decimal itemSubTotalInclTax;
decimal taxRate;
//calculate subtotal for the updated order item
if (shoppingCartItem.Id == updatedOrderItem.Id)
{
//update order item
updatedOrderItem.UnitPriceExclTax = updateOrderParameters.PriceExclTax;
updatedOrderItem.UnitPriceInclTax = updateOrderParameters.PriceInclTax;
updatedOrderItem.DiscountAmountExclTax = updateOrderParameters.DiscountAmountExclTax;
updatedOrderItem.DiscountAmountInclTax = updateOrderParameters.DiscountAmountInclTax;
updatedOrderItem.PriceExclTax = itemSubTotalExclTax = updateOrderParameters.SubTotalExclTax;
updatedOrderItem.PriceInclTax = itemSubTotalInclTax = updateOrderParameters.SubTotalInclTax;
updatedOrderItem.Quantity = shoppingCartItem.Quantity;
taxRate = itemSubTotalExclTax > 0 ? Math.Round(100 * (itemSubTotalInclTax - itemSubTotalExclTax) / itemSubTotalExclTax, 3) : 0M;
}
else
{
//get the already calculated subtotal from the order item
var order = await _orderService.GetOrderItemByIdAsync(shoppingCartItem.Id);
itemSubTotalExclTax = order.PriceExclTax;
itemSubTotalInclTax = order.PriceInclTax;
taxRate = itemSubTotalExclTax > 0 ? Math.Round(100 * (itemSubTotalInclTax - itemSubTotalExclTax) / itemSubTotalExclTax, 3) : 0M;
}
subTotalExclTax += itemSubTotalExclTax;
subTotalInclTax += itemSubTotalInclTax;
//tax rates
var itemTaxValue = itemSubTotalInclTax - itemSubTotalExclTax;
if (taxRate <= decimal.Zero || itemTaxValue <= decimal.Zero)
continue;
if (!subTotalTaxRates.TryGetValue(taxRate, out var value))
subTotalTaxRates.Add(taxRate, itemTaxValue);
else
subTotalTaxRates[taxRate] = value + itemTaxValue;
}
if (subTotalExclTax < decimal.Zero)
subTotalExclTax = decimal.Zero;
if (subTotalInclTax < decimal.Zero)
subTotalInclTax = decimal.Zero;
//We calculate discount amount on order subtotal excl tax (discount first)
//calculate discount amount ('Applied to order subtotal' discount)
var customer = await _customerService.GetCustomerByIdAsync(updatedOrder.CustomerId);
var (discountAmountExclTax, subTotalDiscounts) = await GetOrderSubtotalDiscountAsync(customer, subTotalExclTax);
if (subTotalExclTax < discountAmountExclTax)
discountAmountExclTax = subTotalExclTax;
var discountAmountInclTax = discountAmountExclTax;
//add tax for shopping items
var tempTaxRates = new Dictionary(subTotalTaxRates);
foreach (var kvp in tempTaxRates)
{
if (kvp.Value == decimal.Zero || subTotalExclTax <= decimal.Zero)
continue;
var discountTaxValue = kvp.Value * (discountAmountExclTax / subTotalExclTax);
discountAmountInclTax += discountTaxValue;
subTotalTaxRates[kvp.Key] = kvp.Value - discountTaxValue;
}
//rounding
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
subTotalExclTax = await _priceCalculationService.RoundPriceAsync(subTotalExclTax);
subTotalInclTax = await _priceCalculationService.RoundPriceAsync(subTotalInclTax);
discountAmountExclTax = await _priceCalculationService.RoundPriceAsync(discountAmountExclTax);
discountAmountInclTax = await _priceCalculationService.RoundPriceAsync(discountAmountInclTax);
}
updatedOrder.OrderSubtotalExclTax = subTotalExclTax;
updatedOrder.OrderSubtotalInclTax = subTotalInclTax;
updatedOrder.OrderSubTotalDiscountExclTax = discountAmountExclTax;
updatedOrder.OrderSubTotalDiscountInclTax = discountAmountInclTax;
foreach (var discount in subTotalDiscounts)
if (!_discountService.ContainsDiscount(updateOrderParameters.AppliedDiscounts, discount))
updateOrderParameters.AppliedDiscounts.Add(discount);
return (subTotalExclTax, subTotalInclTax, subTotalTaxRates, discountAmountExclTax);
}
///
/// Set reward points
///
/// Redeemed reward points
/// Redeemed reward points amount
/// A value indicating whether to use reward points
/// Customer
/// Order total
/// A task that represents the asynchronous operation
protected virtual async Task<(int redeemedRewardPoints, decimal redeemedRewardPointsAmount)> SetRewardPointsAsync(int redeemedRewardPoints, decimal redeemedRewardPointsAmount,
bool? useRewardPoints, Customer customer, decimal orderTotal)
{
if (!_rewardPointsSettings.Enabled)
return (redeemedRewardPoints, redeemedRewardPointsAmount);
var store = await _storeContext.GetCurrentStoreAsync();
if (!useRewardPoints.HasValue)
useRewardPoints = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.UseRewardPointsDuringCheckoutAttribute, store.Id);
if (!useRewardPoints.Value)
return (redeemedRewardPoints, redeemedRewardPointsAmount);
if (orderTotal <= decimal.Zero)
return (redeemedRewardPoints, redeemedRewardPointsAmount);
var rewardPointsBalance = await _rewardPointService.GetRewardPointsBalanceAsync(customer.Id, store.Id);
if (_rewardPointsSettings.MaximumRewardPointsToUsePerOrder > 0 && rewardPointsBalance > _rewardPointsSettings.MaximumRewardPointsToUsePerOrder)
rewardPointsBalance = _rewardPointsSettings.MaximumRewardPointsToUsePerOrder;
var rewardPointsBalanceAmount = await ConvertRewardPointsToAmountAsync(rewardPointsBalance);
if (_rewardPointsSettings.MaximumRedeemedRate > 0 && _rewardPointsSettings.MaximumRedeemedRate < rewardPointsBalanceAmount / orderTotal)
{
rewardPointsBalance = ConvertAmountToRewardPoints(orderTotal * _rewardPointsSettings.MaximumRedeemedRate);
rewardPointsBalanceAmount = await ConvertRewardPointsToAmountAsync(rewardPointsBalance);
}
if (!CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance))
return (redeemedRewardPoints, redeemedRewardPointsAmount);
if (orderTotal > rewardPointsBalanceAmount)
{
redeemedRewardPoints = rewardPointsBalance;
redeemedRewardPointsAmount = rewardPointsBalanceAmount;
}
else
{
redeemedRewardPointsAmount = orderTotal;
redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount);
}
return (redeemedRewardPoints, redeemedRewardPointsAmount);
}
///
/// Apply gift cards
///
/// Cart
/// Applied gift cards
/// Customer
///
/// A task that represents the asynchronous operation
protected virtual async Task AppliedGiftCardsAsync(IList cart, List appliedGiftCards,
Customer customer, decimal resultTemp)
{
if (await _shoppingCartService.ShoppingCartIsRecurringAsync(cart))
return resultTemp;
//we don't apply gift cards for recurring products
var giftCards = await _giftCardService.GetActiveGiftCardsAppliedByCustomerAsync(customer);
if (giftCards == null)
return resultTemp;
foreach (var gc in giftCards)
{
if (resultTemp <= decimal.Zero)
continue;
var remainingAmount = await _giftCardService.GetGiftCardRemainingAmountAsync(gc);
var amountCanBeUsed = resultTemp > remainingAmount ? remainingAmount : resultTemp;
//reduce subtotal
resultTemp -= amountCanBeUsed;
var appliedGiftCard = new AppliedGiftCard
{
GiftCard = gc,
AmountCanBeUsed = amountCanBeUsed
};
appliedGiftCards.Add(appliedGiftCard);
}
return resultTemp;
}
///
/// Gets shopping cart additional shipping charge
///
/// Cart
///
/// A task that represents the asynchronous operation
/// The task result contains the additional shipping charge
///
protected virtual async Task GetShoppingCartAdditionalShippingChargeAsync(IList cart)
{
return await cart.SumAwaitAsync(async shoppingCartItem => await _shippingService.GetAdditionalShippingChargeAsync(shoppingCartItem));
}
///
/// Converts an amount to reward points
///
/// Amount
/// Converted value
protected virtual int ConvertAmountToRewardPoints(decimal amount)
{
var result = 0;
if (amount <= 0)
return 0;
if (_rewardPointsSettings.ExchangeRate > 0)
result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate);
return result;
}
#endregion
#region Methods
///
/// Gets shopping cart subtotal
///
/// Cart
/// A value indicating whether calculated price should include tax
///
/// A task that represents the asynchronous operation
/// The task result contains the applied discount amount. Applied discounts. Sub total (without discount). Sub total (with discount). Tax rates (of order sub total)
///
public virtual async Task<(decimal discountAmount, List appliedDiscounts, decimal subTotalWithoutDiscount, decimal subTotalWithDiscount, SortedDictionary taxRates)> GetShoppingCartSubTotalAsync(IList cart,
bool includingTax)
{
var (discountAmountInclTax, discountAmountExclTax, appliedDiscounts, subTotalWithoutDiscountInclTax, subTotalWithoutDiscountExclTax, subTotalWithDiscountInclTax, subTotalWithDiscountExclTax, taxRates) = await GetShoppingCartSubTotalsAsync(cart);
return (includingTax ? discountAmountInclTax : discountAmountExclTax, appliedDiscounts,
includingTax ? subTotalWithoutDiscountInclTax : subTotalWithoutDiscountExclTax,
includingTax ? subTotalWithDiscountInclTax : subTotalWithDiscountExclTax, taxRates);
}
///
/// Gets shopping cart subtotal
///
/// Cart
///
/// A task that represents the asynchronous operation
/// The task result contains the applied discount amount. Applied discounts. Sub total (without discount). Sub total (with discount). Tax rates (of order sub total)
///
public virtual async Task<(decimal discountAmountInclTax, decimal discountAmountExclTax, List
appliedDiscounts, decimal subTotalWithoutDiscountInclTax, decimal subTotalWithoutDiscountExclTax, decimal
subTotalWithDiscountInclTax, decimal subTotalWithDiscountExclTax, SortedDictionary
taxRates)> GetShoppingCartSubTotalsAsync(IList cart)
{
var discountAmountExclTax = decimal.Zero;
var discountAmountInclTax = decimal.Zero;
var appliedDiscounts = new List();
var subTotalWithoutDiscountExclTax = decimal.Zero;
var subTotalWithoutDiscountInclTax = decimal.Zero;
var subTotalWithDiscountExclTax = decimal.Zero;
var subTotalWithDiscountInclTax = decimal.Zero;
var taxRates = new SortedDictionary();
if (!cart.Any())
return (discountAmountInclTax, discountAmountExclTax, appliedDiscounts, subTotalWithoutDiscountInclTax, subTotalWithoutDiscountExclTax, subTotalWithDiscountInclTax, subTotalWithDiscountExclTax, taxRates);
//get the customer
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
//sub totals
foreach (var shoppingCartItem in cart)
{
var sciSubTotal = (await _shoppingCartService.GetSubTotalAsync(shoppingCartItem, true)).subTotal;
var product = await _productService.GetProductByIdAsync(shoppingCartItem.ProductId);
var (sciExclTax, taxRate) = await _taxService.GetProductPriceAsync(product, sciSubTotal, false, customer);
var (sciInclTax, _) = await _taxService.GetProductPriceAsync(product, sciSubTotal, true, customer);
subTotalWithoutDiscountExclTax += sciExclTax;
subTotalWithoutDiscountInclTax += sciInclTax;
//tax rates
var sciTax = sciInclTax - sciExclTax;
if (taxRate <= decimal.Zero || sciTax <= decimal.Zero)
continue;
if (!taxRates.ContainsKey(taxRate))
taxRates.Add(taxRate, sciTax);
else
taxRates[taxRate] += sciTax;
}
//checkout attributes
if (customer != null)
{
var store = await _storeContext.GetCurrentStoreAsync();
var checkoutAttributesXml = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.CheckoutAttributes, store.Id);
var attributeValues = _checkoutAttributeParser.ParseAttributeValues(checkoutAttributesXml);
if (attributeValues != null)
{
await foreach (var (attribute, values) in attributeValues)
{
await foreach (var attributeValue in values)
{
var (caExclTax, taxRate) = await _taxService.GetCheckoutAttributePriceAsync(attribute, attributeValue, false, customer);
var (caInclTax, _) = await _taxService.GetCheckoutAttributePriceAsync(attribute, attributeValue, true, customer);
subTotalWithoutDiscountExclTax += caExclTax;
subTotalWithoutDiscountInclTax += caInclTax;
//tax rates
var caTax = caInclTax - caExclTax;
if (taxRate <= decimal.Zero || caTax <= decimal.Zero)
continue;
if (!taxRates.ContainsKey(taxRate))
taxRates.Add(taxRate, caTax);
else
taxRates[taxRate] += caTax;
}
}
}
}
if (subTotalWithoutDiscountExclTax < decimal.Zero)
subTotalWithoutDiscountExclTax = decimal.Zero;
if (subTotalWithoutDiscountInclTax < decimal.Zero)
subTotalWithoutDiscountInclTax = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
subTotalWithoutDiscountInclTax = await _priceCalculationService.RoundPriceAsync(subTotalWithoutDiscountInclTax);
subTotalWithoutDiscountExclTax = await _priceCalculationService.RoundPriceAsync(subTotalWithoutDiscountExclTax);
}
//We calculate discount amount on order subtotal excl tax (discount first)
//calculate discount amount ('Applied to order subtotal' discount)
(discountAmountExclTax, appliedDiscounts) = await GetOrderSubtotalDiscountAsync(customer, subTotalWithoutDiscountExclTax);
if (subTotalWithoutDiscountExclTax < discountAmountExclTax)
discountAmountExclTax = subTotalWithoutDiscountExclTax;
discountAmountInclTax = discountAmountExclTax;
//subtotal with discount (excl tax)
subTotalWithDiscountExclTax = subTotalWithoutDiscountExclTax - discountAmountExclTax;
subTotalWithDiscountInclTax = subTotalWithDiscountExclTax;
//add tax for shopping items & checkout attributes
var tempTaxRates = new Dictionary(taxRates);
foreach (var kvp in tempTaxRates)
{
var taxRate = kvp.Key;
var taxValue = kvp.Value;
if (taxValue == decimal.Zero)
continue;
//discount the tax amount that applies to subtotal items
if (subTotalWithoutDiscountExclTax > decimal.Zero)
{
var discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalWithoutDiscountExclTax);
discountAmountInclTax += discountTax;
taxValue = taxRates[taxRate] - discountTax;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
taxValue = await _priceCalculationService.RoundPriceAsync(taxValue);
taxRates[taxRate] = taxValue;
}
//subtotal with discount (incl tax)
subTotalWithDiscountInclTax += taxValue;
}
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
discountAmountInclTax = await _priceCalculationService.RoundPriceAsync(discountAmountInclTax);
discountAmountExclTax = await _priceCalculationService.RoundPriceAsync(discountAmountExclTax);
}
if (subTotalWithDiscountExclTax < decimal.Zero)
subTotalWithDiscountExclTax = decimal.Zero;
if (subTotalWithDiscountInclTax < decimal.Zero)
subTotalWithDiscountInclTax = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
subTotalWithDiscountExclTax = await _priceCalculationService.RoundPriceAsync(subTotalWithDiscountExclTax);
subTotalWithDiscountInclTax = await _priceCalculationService.RoundPriceAsync(subTotalWithDiscountInclTax);
}
return (discountAmountInclTax, discountAmountExclTax, appliedDiscounts, subTotalWithoutDiscountInclTax, subTotalWithoutDiscountExclTax, subTotalWithDiscountInclTax, subTotalWithDiscountExclTax, taxRates);
}
///
/// Update order totals
///
/// Parameters for the updating order
/// Shopping cart
/// A task that represents the asynchronous operation
public virtual async Task UpdateOrderTotalsAsync(UpdateOrderParameters updateOrderParameters, IList restoredCart)
{
//sub total
var (subTotalExclTax, subTotalInclTax, subTotalTaxRates, discountAmountExclTax) = await UpdateSubTotalAsync(updateOrderParameters, restoredCart);
//shipping
var (shippingTotalExclTax, shippingTotalInclTax, shippingTaxRate) = await UpdateShippingAsync(updateOrderParameters, restoredCart, subTotalInclTax, subTotalExclTax);
//tax rates
var taxTotal = await UpdateTaxRatesAsync(subTotalTaxRates, shippingTotalInclTax, shippingTotalExclTax, shippingTaxRate, updateOrderParameters.UpdatedOrder);
//total
await UpdateTotalAsync(updateOrderParameters, subTotalExclTax, discountAmountExclTax, shippingTotalExclTax, taxTotal);
}
///
/// Gets a value indicating whether shipping is free
///
/// Cart
/// Subtotal amount; pass null to calculate subtotal
///
/// A task that represents the asynchronous operation
/// The task result contains a value indicating whether shipping is free
///
public virtual async Task IsFreeShippingAsync(IList cart, decimal? subTotal = null)
{
//check whether customer is in a customer role with free shipping applied
var customer = await _customerService.GetCustomerByIdAsync(cart.FirstOrDefault()?.CustomerId ?? 0);
if (customer != null && (await _customerService.GetCustomerRolesAsync(customer)).Any(role => role.FreeShipping))
return true;
//check whether all shopping cart items and their associated products marked as free shipping
if (await cart.AllAwaitAsync(async shoppingCartItem => await _shippingService.IsFreeShippingAsync(shoppingCartItem)))
return true;
//free shipping over $X
if (!_shippingSettings.FreeShippingOverXEnabled)
return false;
if (!subTotal.HasValue)
{
var (_, _, _, subTotalWithDiscount, _) = await GetShoppingCartSubTotalAsync(cart, _shippingSettings.FreeShippingOverXIncludingTax);
subTotal = subTotalWithDiscount;
}
//check whether we have subtotal enough to have free shipping
if (subTotal.Value > _shippingSettings.FreeShippingOverXValue)
return true;
return false;
}
///
/// Adjust shipping rate (free shipping, additional charges, discounts)
///
/// Shipping rate to adjust
/// Cart
/// Adjust shipping rate to pickup in store shipping option rate
///
/// A task that represents the asynchronous operation
/// The task result contains the adjusted shipping rate. Applied discounts
///
public virtual async Task<(decimal adjustedShippingRate, List appliedDiscounts)> AdjustShippingRateAsync(decimal shippingRate, IList cart,
bool applyToPickupInStore = false)
{
//free shipping
if (await IsFreeShippingAsync(cart))
return (decimal.Zero, new List());
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
var store = await _storeContext.GetCurrentStoreAsync();
//with additional shipping charges
var pickupPoint = await _genericAttributeService.GetAttributeAsync(customer,
NopCustomerDefaults.SelectedPickupPointAttribute, store.Id);
var adjustedRate = shippingRate;
if (!(applyToPickupInStore && _shippingSettings.AllowPickupInStore && pickupPoint != null && _shippingSettings.IgnoreAdditionalShippingChargeForPickupInStore))
{
adjustedRate += await GetShoppingCartAdditionalShippingChargeAsync(cart);
}
//discount
var (discountAmount, appliedDiscounts) = await GetShippingDiscountAsync(customer, adjustedRate);
adjustedRate -= discountAmount;
adjustedRate = Math.Max(adjustedRate, decimal.Zero);
if (_shoppingCartSettings.RoundPricesDuringCalculation)
adjustedRate = await _priceCalculationService.RoundPriceAsync(adjustedRate);
return (adjustedRate, appliedDiscounts);
}
///
/// Gets shopping cart shipping total
///
/// Cart
///
/// A task that represents the asynchronous operation
/// The task result contains the shipping total
///
public virtual async Task GetShoppingCartShippingTotalAsync(IList cart)
{
var includingTax = await _workContext.GetTaxDisplayTypeAsync() == TaxDisplayType.IncludingTax;
return (await GetShoppingCartShippingTotalAsync(cart, includingTax)).shippingTotal;
}
///
/// Gets shopping cart shipping total
///
/// Cart
/// A value indicating whether calculated price should include tax
///
/// A task that represents the asynchronous operation
/// The task result contains the shipping total. Applied tax rate. Applied discounts
///
public virtual async Task<(decimal? shippingTotal, decimal taxRate, List appliedDiscounts)> GetShoppingCartShippingTotalAsync(IList cart, bool includingTax)
{
decimal? shippingTotal = null;
var appliedDiscounts = new List();
var taxRate = decimal.Zero;
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
var isFreeShipping = await IsFreeShippingAsync(cart);
if (isFreeShipping)
return (decimal.Zero, taxRate, appliedDiscounts);
ShippingOption shippingOption = null;
var store = await _storeContext.GetCurrentStoreAsync();
if (customer != null)
shippingOption = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.SelectedShippingOptionAttribute, store.Id);
if (shippingOption != null)
{
//use last shipping option (get from cache)
(shippingTotal, appliedDiscounts) = await AdjustShippingRateAsync(shippingOption.Rate, cart, shippingOption.IsPickupInStore);
}
else
{
//use fixed rate (if possible)
Address shippingAddress = null;
if (customer != null)
shippingAddress = await _customerService.GetCustomerShippingAddressAsync(customer);
var shippingRateComputationMethods = await _shippingPluginManager.LoadActivePluginsAsync(await _workContext.GetCurrentCustomerAsync(), store.Id);
if (!shippingRateComputationMethods.Any() && !_shippingSettings.AllowPickupInStore)
throw new NopException("Shipping rate computation method could not be loaded");
if (shippingRateComputationMethods.Count == 1)
{
var shippingRateComputationMethod = shippingRateComputationMethods[0];
var shippingOptionRequests = (await _shippingService.CreateShippingOptionRequestsAsync(cart,
shippingAddress,
store.Id)).shipmentPackages;
decimal? fixedRate = null;
foreach (var shippingOptionRequest in shippingOptionRequests)
{
//calculate fixed rates for each request-package
var fixedRateTmp = await shippingRateComputationMethod.GetFixedRateAsync(shippingOptionRequest);
if (!fixedRateTmp.HasValue)
continue;
if (!fixedRate.HasValue)
fixedRate = decimal.Zero;
fixedRate += fixedRateTmp.Value;
}
if (fixedRate.HasValue)
{
//adjust shipping rate
(shippingTotal, appliedDiscounts) = await AdjustShippingRateAsync(fixedRate.Value, cart);
}
}
}
if (!shippingTotal.HasValue)
return (null, taxRate, appliedDiscounts);
if (shippingTotal.Value < decimal.Zero)
shippingTotal = decimal.Zero;
//round
if (_shoppingCartSettings.RoundPricesDuringCalculation)
shippingTotal = await _priceCalculationService.RoundPriceAsync(shippingTotal.Value);
decimal? shippingTotalTaxed;
(shippingTotalTaxed, taxRate) = await _taxService.GetShippingPriceAsync(shippingTotal.Value,
includingTax,
customer);
//round
if (_shoppingCartSettings.RoundPricesDuringCalculation)
shippingTotalTaxed = await _priceCalculationService.RoundPriceAsync(shippingTotalTaxed.Value);
return (shippingTotalTaxed, taxRate, appliedDiscounts);
}
///
/// Gets shopping cart shipping total
///
/// Cart
///
/// A task that represents the asynchronous operation
/// The task result contains the shipping total. Applied tax rate. Applied discounts
///
public virtual async Task<(decimal? shippingTotalInclTax, decimal? shippingTotaExclTax, decimal taxRate, List appliedDiscounts)> GetShoppingCartShippingTotalsAsync(IList cart)
{
decimal? shippingTotal = null;
var appliedDiscounts = new List();
var taxRate = decimal.Zero;
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
var isFreeShipping = await IsFreeShippingAsync(cart);
if (isFreeShipping)
return (decimal.Zero, decimal.Zero, taxRate, appliedDiscounts);
ShippingOption shippingOption = null;
var store = await _storeContext.GetCurrentStoreAsync();
if (customer != null)
shippingOption = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.SelectedShippingOptionAttribute, store.Id);
if (shippingOption != null)
{
//use last shipping option (get from cache)
(shippingTotal, appliedDiscounts) = await AdjustShippingRateAsync(shippingOption.Rate, cart, shippingOption.IsPickupInStore);
}
else
{
//use fixed rate (if possible)
Address shippingAddress = null;
if (customer != null)
shippingAddress = await _customerService.GetCustomerShippingAddressAsync(customer);
var shippingRateComputationMethods = await _shippingPluginManager.LoadActivePluginsAsync(await _workContext.GetCurrentCustomerAsync(), store.Id);
if (!shippingRateComputationMethods.Any() && !_shippingSettings.AllowPickupInStore)
throw new NopException("Shipping rate computation method could not be loaded");
if (shippingRateComputationMethods.Count == 1)
{
var shippingRateComputationMethod = shippingRateComputationMethods[0];
var shippingOptionRequests = (await _shippingService.CreateShippingOptionRequestsAsync(cart,
shippingAddress,
store.Id)).shipmentPackages;
decimal? fixedRate = null;
foreach (var shippingOptionRequest in shippingOptionRequests)
{
//calculate fixed rates for each request-package
var fixedRateTmp = await shippingRateComputationMethod.GetFixedRateAsync(shippingOptionRequest);
if (!fixedRateTmp.HasValue)
continue;
if (!fixedRate.HasValue)
fixedRate = decimal.Zero;
fixedRate += fixedRateTmp.Value;
}
if (fixedRate.HasValue)
{
//adjust shipping rate
(shippingTotal, appliedDiscounts) = await AdjustShippingRateAsync(fixedRate.Value, cart);
}
}
}
if (!shippingTotal.HasValue)
return (null, null, taxRate, appliedDiscounts);
if (shippingTotal.Value < decimal.Zero)
shippingTotal = decimal.Zero;
//round
if (_shoppingCartSettings.RoundPricesDuringCalculation)
shippingTotal = await _priceCalculationService.RoundPriceAsync(shippingTotal.Value);
decimal? shippingTotalTaxedInclTaxt, shippingTotalTaxedExclTaxt;
(shippingTotalTaxedInclTaxt, taxRate) = await _taxService.GetShippingPriceAsync(shippingTotal.Value,
true,
customer);
(shippingTotalTaxedExclTaxt, _) = await _taxService.GetShippingPriceAsync(shippingTotal.Value,
false,
customer);
//round
if (_shoppingCartSettings.RoundPricesDuringCalculation)
{
shippingTotalTaxedInclTaxt = await _priceCalculationService.RoundPriceAsync(shippingTotalTaxedInclTaxt.Value);
shippingTotalTaxedExclTaxt = await _priceCalculationService.RoundPriceAsync(shippingTotalTaxedExclTaxt.Value);
}
return (shippingTotalTaxedInclTaxt, shippingTotalTaxedExclTaxt, taxRate, appliedDiscounts);
}
///
/// Gets tax
///
/// Shopping cart
/// A value indicating whether we should use payment method additional fee when calculating tax
///
/// A task that represents the asynchronous operation
/// The task result contains the ax total, Tax rates
///
public virtual async Task<(decimal taxTotal, SortedDictionary taxRates)> GetTaxTotalAsync(IList cart, bool usePaymentMethodAdditionalFee = true)
{
ArgumentNullException.ThrowIfNull(cart);
var taxTotalResult = await _taxService.GetTaxTotalAsync(cart, usePaymentMethodAdditionalFee);
var taxRates = taxTotalResult?.TaxRates ?? new SortedDictionary();
var taxTotal = taxTotalResult?.TaxTotal ?? decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
taxTotal = await _priceCalculationService.RoundPriceAsync(taxTotal);
return (taxTotal, taxRates);
}
///
/// Gets shopping cart total
///
/// Cart
/// A value indicating reward points should be used; null to detect current choice of the customer
/// A value indicating whether we should use payment method additional fee when calculating order total
///
/// A task that represents the asynchronous operation
/// The task result contains the shopping cart total;Null if shopping cart total couldn't be calculated now. Applied gift cards. Applied discount amount. Applied discounts. Reward points to redeem. Reward points amount in primary store currency to redeem
///
public virtual async Task<(decimal? shoppingCartTotal, decimal discountAmount, List appliedDiscounts, List appliedGiftCards, int redeemedRewardPoints, decimal redeemedRewardPointsAmount)> GetShoppingCartTotalAsync(IList cart,
bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true)
{
var redeemedRewardPoints = 0;
var redeemedRewardPointsAmount = decimal.Zero;
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
var store = await _storeContext.GetCurrentStoreAsync();
var paymentMethodSystemName = string.Empty;
if (customer != null)
{
paymentMethodSystemName = await _genericAttributeService.GetAttributeAsync(customer,
NopCustomerDefaults.SelectedPaymentMethodAttribute, store.Id);
}
//subtotal without tax
var (_, _, _, subTotalWithDiscountBase, _) = await GetShoppingCartSubTotalAsync(cart, false);
//subtotal with discount
var subtotalBase = subTotalWithDiscountBase;
//shipping without tax
var shoppingCartShipping = (await GetShoppingCartShippingTotalAsync(cart, false)).shippingTotal;
//payment method additional fee without tax
var paymentMethodAdditionalFeeWithoutTax = decimal.Zero;
if (usePaymentMethodAdditionalFee && !string.IsNullOrEmpty(paymentMethodSystemName))
{
var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFeeAsync(cart,
paymentMethodSystemName);
paymentMethodAdditionalFeeWithoutTax =
(await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee,
false, customer)).price;
}
//tax
var shoppingCartTax = (await GetTaxTotalAsync(cart, usePaymentMethodAdditionalFee)).taxTotal;
//order total
var resultTemp = decimal.Zero;
resultTemp += subtotalBase;
if (shoppingCartShipping.HasValue)
{
resultTemp += shoppingCartShipping.Value;
}
resultTemp += paymentMethodAdditionalFeeWithoutTax;
resultTemp += shoppingCartTax;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
resultTemp = await _priceCalculationService.RoundPriceAsync(resultTemp);
//order total discount
var (discountAmount, appliedDiscounts) = await GetOrderTotalDiscountAsync(customer, resultTemp);
//sub totals with discount
if (resultTemp < discountAmount)
discountAmount = resultTemp;
//reduce subtotal
resultTemp -= discountAmount;
if (resultTemp < decimal.Zero)
resultTemp = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
resultTemp = await _priceCalculationService.RoundPriceAsync(resultTemp);
//let's apply gift cards now (gift cards that can be used)
var appliedGiftCards = new List();
resultTemp = await AppliedGiftCardsAsync(cart, appliedGiftCards, customer, resultTemp);
if (resultTemp < decimal.Zero)
resultTemp = decimal.Zero;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
resultTemp = await _priceCalculationService.RoundPriceAsync(resultTemp);
if (!shoppingCartShipping.HasValue)
{
//we have errors
return (null, discountAmount, appliedDiscounts, appliedGiftCards, redeemedRewardPoints, redeemedRewardPointsAmount);
}
var orderTotal = resultTemp;
//reward points
(redeemedRewardPoints, redeemedRewardPointsAmount) = await SetRewardPointsAsync(redeemedRewardPoints, redeemedRewardPointsAmount, useRewardPoints, customer, orderTotal);
orderTotal -= redeemedRewardPointsAmount;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
orderTotal = await _priceCalculationService.RoundPriceAsync(orderTotal);
return (orderTotal, discountAmount, appliedDiscounts, appliedGiftCards, redeemedRewardPoints, redeemedRewardPointsAmount);
}
///
/// Calculate payment method fee
///
/// Cart
/// Fee value
/// Is fee amount specified as percentage or fixed value?
///
/// A task that represents the asynchronous operation
/// The task result contains the result
///
public virtual async Task CalculatePaymentAdditionalFeeAsync(IList cart, decimal fee, bool usePercentage)
{
if (!usePercentage || fee <= 0)
return fee;
var orderTotalWithoutPaymentFee = (await GetShoppingCartTotalAsync(cart, usePaymentMethodAdditionalFee: false)).shoppingCartTotal ?? 0;
var result = (decimal)((float)orderTotalWithoutPaymentFee * (float)fee / 100f);
return result;
}
///
/// Converts existing reward points to amount
///
/// Reward points
///
/// A task that represents the asynchronous operation
/// The task result contains the converted value
///
public virtual async Task ConvertRewardPointsToAmountAsync(int rewardPoints)
{
if (rewardPoints <= 0)
return decimal.Zero;
var result = rewardPoints * _rewardPointsSettings.ExchangeRate;
if (_shoppingCartSettings.RoundPricesDuringCalculation)
result = await _priceCalculationService.RoundPriceAsync(result);
return result;
}
///
/// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled)
///
/// Reward points to check
/// true - reward points could use; false - cannot be used.
public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints)
{
if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0)
return true;
return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse;
}
///
/// Calculate how order total (maximum amount) for which reward points could be earned/reduced
///
/// Order shipping (including tax)
/// Order total
/// Applicable order total
public virtual decimal CalculateApplicableOrderTotalForRewardPoints(decimal orderShippingInclTax, decimal orderTotal)
{
//do you give reward points for order total? or do you exclude shipping?
//since shipping costs vary some of store owners don't give reward points based on shipping total
//you can put your custom logic here
var totalForRewardPoints = orderTotal - orderShippingInclTax;
//check the minimum total to award points
if (totalForRewardPoints < _rewardPointsSettings.MinOrderTotalToAwardPoints)
return decimal.Zero;
return totalForRewardPoints;
}
///
/// Calculate how much reward points will be earned/reduced based on certain amount spent
///
/// Customer
/// Amount (in primary store currency)
///
/// A task that represents the asynchronous operation
/// The task result contains the number of reward points
///
public virtual async Task CalculateRewardPointsAsync(Customer customer, decimal amount)
{
if (!_rewardPointsSettings.Enabled)
return 0;
if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero)
return 0;
//ensure that reward points are applied only to registered users
if (customer == null || await _customerService.IsGuestAsync(customer))
return 0;
var points = (int)Math.Truncate(amount / _rewardPointsSettings.PointsForPurchases_Amount) * _rewardPointsSettings.PointsForPurchases_Points;
return points;
}
#endregion
}