1505 lines
69 KiB
C#
1505 lines
69 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Order service
|
|
/// </summary>
|
|
public partial class OrderTotalCalculationService : IOrderTotalCalculationService
|
|
{
|
|
#region Fields
|
|
|
|
protected readonly CatalogSettings _catalogSettings;
|
|
protected readonly IAddressService _addressService;
|
|
protected readonly IAttributeParser<CheckoutAttribute, CheckoutAttributeValue> _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<CheckoutAttribute, CheckoutAttributeValue> 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
|
|
|
|
/// <summary>
|
|
/// Gets an order discount (applied to order subtotal)
|
|
/// </summary>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="orderSubTotal">Order subtotal</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the order discount, Applied discounts
|
|
/// </returns>
|
|
protected virtual async Task<(decimal orderDiscount, List<Discount> appliedDiscounts)> GetOrderSubtotalDiscountAsync(Customer customer,
|
|
decimal orderSubTotal)
|
|
{
|
|
var discountAmount = decimal.Zero;
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return (discountAmount, new List<Discount>());
|
|
|
|
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToOrderSubTotal);
|
|
var allowedDiscounts = new List<Discount>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a shipping discount
|
|
/// </summary>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="shippingTotal">Shipping total</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the shipping discount. Applied discounts
|
|
/// </returns>
|
|
protected virtual async Task<(decimal shippingDiscount, List<Discount> appliedDiscounts)> GetShippingDiscountAsync(Customer customer, decimal shippingTotal)
|
|
{
|
|
var appliedDiscounts = new List<Discount>();
|
|
var shippingDiscountAmount = decimal.Zero;
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return (shippingDiscountAmount, appliedDiscounts);
|
|
|
|
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToShipping);
|
|
var allowedDiscounts = new List<Discount>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an order discount (applied to order total)
|
|
/// </summary>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="orderTotal">Order total</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the order discount. Applied discounts
|
|
/// </returns>
|
|
protected virtual async Task<(decimal orderDiscount, List<Discount> appliedDiscounts)> GetOrderTotalDiscountAsync(Customer customer, decimal orderTotal)
|
|
{
|
|
var discountAmount = decimal.Zero;
|
|
if (_catalogSettings.IgnoreDiscounts)
|
|
return (discountAmount, new List<Discount>());
|
|
|
|
var allDiscounts = await _discountService.GetAllDiscountsAsync(DiscountType.AssignedToOrderTotal);
|
|
var allowedDiscounts = new List<Discount>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update order total
|
|
/// </summary>
|
|
/// <param name="updateOrderParameters">UpdateOrderParameters</param>
|
|
/// <param name="subTotalExclTax">Subtotal (excl tax)</param>
|
|
/// <param name="discountAmountExclTax">Discount amount (excl tax)</param>
|
|
/// <param name="shippingTotalExclTax">Shipping (excl tax)</param>
|
|
/// <param name="taxTotal">Tax</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update tax rates
|
|
/// </summary>
|
|
/// <param name="subTotalTaxRates">Subtotal tax rates</param>
|
|
/// <param name="shippingTotalInclTax">Shipping (incl tax)</param>
|
|
/// <param name="shippingTotalExclTax">Shipping (excl tax)</param>
|
|
/// <param name="shippingTaxRate">Shipping tax rates</param>
|
|
/// <param name="updatedOrder">Order</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the ax total
|
|
/// </returns>
|
|
protected virtual async Task<decimal> UpdateTaxRatesAsync(SortedDictionary<decimal, decimal> subTotalTaxRates, decimal shippingTotalInclTax,
|
|
decimal shippingTotalExclTax, decimal shippingTaxRate, Order updatedOrder)
|
|
{
|
|
var taxRates = new SortedDictionary<decimal, decimal>();
|
|
|
|
//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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update shipping
|
|
/// </summary>
|
|
/// <param name="updateOrderParameters">UpdateOrderParameters</param>
|
|
/// <param name="restoredCart">Cart</param>
|
|
/// <param name="subTotalInclTax">Subtotal (incl tax)</param>
|
|
/// <param name="subTotalExclTax">Subtotal (excl tax)</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the shipping total. Shipping (incl tax). Shipping tax rate
|
|
/// </returns>
|
|
protected virtual async Task<(decimal shippingTotal, decimal shippingTotalInclTax, decimal shippingTaxRate)> UpdateShippingAsync(UpdateOrderParameters updateOrderParameters, IList<ShoppingCartItem> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update order parameters
|
|
/// </summary>
|
|
/// <param name="updateOrderParameters">UpdateOrderParameters</param>
|
|
/// <param name="restoredCart">Cart</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the subtotal. Subtotal (incl tax). Subtotal tax rates. Discount amount (excl tax)
|
|
/// </returns>
|
|
protected virtual async Task<(decimal subtotal, decimal subTotalInclTax, SortedDictionary<decimal, decimal> subTotalTaxRates, decimal discountAmountExclTax)> UpdateSubTotalAsync(UpdateOrderParameters updateOrderParameters, IList<ShoppingCartItem> restoredCart)
|
|
{
|
|
var subTotalExclTax = decimal.Zero;
|
|
var subTotalInclTax = decimal.Zero;
|
|
var subTotalTaxRates = new SortedDictionary<decimal, decimal>();
|
|
|
|
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<decimal, decimal>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set reward points
|
|
/// </summary>
|
|
/// <param name="redeemedRewardPoints">Redeemed reward points</param>
|
|
/// <param name="redeemedRewardPointsAmount">Redeemed reward points amount</param>
|
|
/// <param name="useRewardPoints">A value indicating whether to use reward points</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="orderTotal">Order total</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
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<bool>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply gift cards
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="appliedGiftCards">Applied gift cards</param>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="resultTemp"></param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<decimal> AppliedGiftCardsAsync(IList<ShoppingCartItem> cart, List<AppliedGiftCard> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart additional shipping charge
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the additional shipping charge
|
|
/// </returns>
|
|
protected virtual async Task<decimal> GetShoppingCartAdditionalShippingChargeAsync(IList<ShoppingCartItem> cart)
|
|
{
|
|
return await cart.SumAwaitAsync(async shoppingCartItem => await _shippingService.GetAdditionalShippingChargeAsync(shoppingCartItem));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an amount to reward points
|
|
/// </summary>
|
|
/// <param name="amount">Amount</param>
|
|
/// <returns>Converted value</returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart subtotal
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="includingTax">A value indicating whether calculated price should include tax</param>
|
|
/// <returns>
|
|
/// 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)
|
|
/// </returns>
|
|
public virtual async Task<(decimal discountAmount, List<Discount> appliedDiscounts, decimal subTotalWithoutDiscount, decimal subTotalWithDiscount, SortedDictionary<decimal, decimal> taxRates)> GetShoppingCartSubTotalAsync(IList<ShoppingCartItem> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart subtotal
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <returns>
|
|
/// 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)
|
|
/// </returns>
|
|
public virtual async Task<(decimal discountAmountInclTax, decimal discountAmountExclTax, List<Discount>
|
|
appliedDiscounts, decimal subTotalWithoutDiscountInclTax, decimal subTotalWithoutDiscountExclTax, decimal
|
|
subTotalWithDiscountInclTax, decimal subTotalWithDiscountExclTax, SortedDictionary<decimal, decimal>
|
|
taxRates)> GetShoppingCartSubTotalsAsync(IList<ShoppingCartItem> cart)
|
|
{
|
|
var discountAmountExclTax = decimal.Zero;
|
|
var discountAmountInclTax = decimal.Zero;
|
|
var appliedDiscounts = new List<Discount>();
|
|
var subTotalWithoutDiscountExclTax = decimal.Zero;
|
|
var subTotalWithoutDiscountInclTax = decimal.Zero;
|
|
|
|
var subTotalWithDiscountExclTax = decimal.Zero;
|
|
var subTotalWithDiscountInclTax = decimal.Zero;
|
|
|
|
var taxRates = new SortedDictionary<decimal, decimal>();
|
|
|
|
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<string>(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<decimal, decimal>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update order totals
|
|
/// </summary>
|
|
/// <param name="updateOrderParameters">Parameters for the updating order</param>
|
|
/// <param name="restoredCart">Shopping cart</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task UpdateOrderTotalsAsync(UpdateOrderParameters updateOrderParameters, IList<ShoppingCartItem> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether shipping is free
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="subTotal">Subtotal amount; pass null to calculate subtotal</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains a value indicating whether shipping is free
|
|
/// </returns>
|
|
public virtual async Task<bool> IsFreeShippingAsync(IList<ShoppingCartItem> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust shipping rate (free shipping, additional charges, discounts)
|
|
/// </summary>
|
|
/// <param name="shippingRate">Shipping rate to adjust</param>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="applyToPickupInStore">Adjust shipping rate to pickup in store shipping option rate</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the adjusted shipping rate. Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal adjustedShippingRate, List<Discount> appliedDiscounts)> AdjustShippingRateAsync(decimal shippingRate, IList<ShoppingCartItem> cart,
|
|
bool applyToPickupInStore = false)
|
|
{
|
|
//free shipping
|
|
if (await IsFreeShippingAsync(cart))
|
|
return (decimal.Zero, new List<Discount>());
|
|
|
|
var customer = await _customerService.GetShoppingCartCustomerAsync(cart);
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
//with additional shipping charges
|
|
var pickupPoint = await _genericAttributeService.GetAttributeAsync<PickupPoint>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart shipping total
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the shipping total
|
|
/// </returns>
|
|
public virtual async Task<decimal?> GetShoppingCartShippingTotalAsync(IList<ShoppingCartItem> cart)
|
|
{
|
|
var includingTax = await _workContext.GetTaxDisplayTypeAsync() == TaxDisplayType.IncludingTax;
|
|
return (await GetShoppingCartShippingTotalAsync(cart, includingTax)).shippingTotal;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart shipping total
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="includingTax">A value indicating whether calculated price should include tax</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the shipping total. Applied tax rate. Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal? shippingTotal, decimal taxRate, List<Discount> appliedDiscounts)> GetShoppingCartShippingTotalAsync(IList<ShoppingCartItem> cart, bool includingTax)
|
|
{
|
|
decimal? shippingTotal = null;
|
|
var appliedDiscounts = new List<Discount>();
|
|
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<ShippingOption>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart shipping total
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the shipping total. Applied tax rate. Applied discounts
|
|
/// </returns>
|
|
public virtual async Task<(decimal? shippingTotalInclTax, decimal? shippingTotaExclTax, decimal taxRate, List<Discount> appliedDiscounts)> GetShoppingCartShippingTotalsAsync(IList<ShoppingCartItem> cart)
|
|
{
|
|
decimal? shippingTotal = null;
|
|
var appliedDiscounts = new List<Discount>();
|
|
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<ShippingOption>(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets tax
|
|
/// </summary>
|
|
/// <param name="cart">Shopping cart</param>
|
|
/// <param name="usePaymentMethodAdditionalFee">A value indicating whether we should use payment method additional fee when calculating tax</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the ax total, Tax rates
|
|
/// </returns>
|
|
public virtual async Task<(decimal taxTotal, SortedDictionary<decimal, decimal> taxRates)> GetTaxTotalAsync(IList<ShoppingCartItem> cart, bool usePaymentMethodAdditionalFee = true)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(cart);
|
|
|
|
var taxTotalResult = await _taxService.GetTaxTotalAsync(cart, usePaymentMethodAdditionalFee);
|
|
var taxRates = taxTotalResult?.TaxRates ?? new SortedDictionary<decimal, decimal>();
|
|
var taxTotal = taxTotalResult?.TaxTotal ?? decimal.Zero;
|
|
|
|
if (_shoppingCartSettings.RoundPricesDuringCalculation)
|
|
taxTotal = await _priceCalculationService.RoundPriceAsync(taxTotal);
|
|
|
|
return (taxTotal, taxRates);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets shopping cart total
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="useRewardPoints">A value indicating reward points should be used; null to detect current choice of the customer</param>
|
|
/// <param name="usePaymentMethodAdditionalFee">A value indicating whether we should use payment method additional fee when calculating order total</param>
|
|
/// <returns>
|
|
/// 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
|
|
/// </returns>
|
|
public virtual async Task<(decimal? shoppingCartTotal, decimal discountAmount, List<Discount> appliedDiscounts, List<AppliedGiftCard> appliedGiftCards, int redeemedRewardPoints, decimal redeemedRewardPointsAmount)> GetShoppingCartTotalAsync(IList<ShoppingCartItem> 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<string>(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<AppliedGiftCard>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate payment method fee
|
|
/// </summary>
|
|
/// <param name="cart">Cart</param>
|
|
/// <param name="fee">Fee value</param>
|
|
/// <param name="usePercentage">Is fee amount specified as percentage or fixed value?</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the result
|
|
/// </returns>
|
|
public virtual async Task<decimal> CalculatePaymentAdditionalFeeAsync(IList<ShoppingCartItem> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts existing reward points to amount
|
|
/// </summary>
|
|
/// <param name="rewardPoints">Reward points</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the converted value
|
|
/// </returns>
|
|
public virtual async Task<decimal> ConvertRewardPointsToAmountAsync(int rewardPoints)
|
|
{
|
|
if (rewardPoints <= 0)
|
|
return decimal.Zero;
|
|
|
|
var result = rewardPoints * _rewardPointsSettings.ExchangeRate;
|
|
if (_shoppingCartSettings.RoundPricesDuringCalculation)
|
|
result = await _priceCalculationService.RoundPriceAsync(result);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled)
|
|
/// </summary>
|
|
/// <param name="rewardPoints">Reward points to check</param>
|
|
/// <returns>true - reward points could use; false - cannot be used.</returns>
|
|
public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints)
|
|
{
|
|
if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0)
|
|
return true;
|
|
|
|
return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate how order total (maximum amount) for which reward points could be earned/reduced
|
|
/// </summary>
|
|
/// <param name="orderShippingInclTax">Order shipping (including tax)</param>
|
|
/// <param name="orderTotal">Order total</param>
|
|
/// <returns>Applicable order total</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculate how much reward points will be earned/reduced based on certain amount spent
|
|
/// </summary>
|
|
/// <param name="customer">Customer</param>
|
|
/// <param name="amount">Amount (in primary store currency)</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the number of reward points
|
|
/// </returns>
|
|
public virtual async Task<int> 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
|
|
} |