public virtual async Task <ShoppingCartTotal> GetShoppingCartTotalAsync( IList <OrganizedShoppingCartItem> cart, bool includeRewardPoints = true, bool includePaymentFee = true, bool includeCreditBalance = true) { Guard.NotNull(cart, nameof(cart)); var store = _storeContext.CurrentStore; var customer = cart.GetCustomer(); var paymentMethodSystemName = customer != null ? customer.GenericAttributes.SelectedPaymentMethod : string.Empty; var(_, subTotalWithDiscount, _, _, _) = await GetCartSubTotalAsync(cart, false); // Subtotal with discount. var subTotalBase = subTotalWithDiscount; // Shipping without tax. var(shoppingCartShipping, _, _) = await GetCartShippingTotalAsync(cart, false); // Payment method additional fee without tax. var paymentFeeWithoutTax = decimal.Zero; if (includePaymentFee && paymentMethodSystemName.HasValue()) { var paymentFee = await GetShoppingCartPaymentFeeAsync(cart, paymentMethodSystemName); if (paymentFee != decimal.Zero) { var(paymentFeeExclTax, _) = await _taxService.GetPaymentMethodFeeAsync(paymentFee, false, customer : customer); paymentFeeWithoutTax = paymentFeeExclTax.Amount; } } // Tax. var(shoppingCartTax, _) = await GetCartTaxTotalAsync(cart, includePaymentFee); // Order total. var resultTemp = subTotalBase; if (shoppingCartShipping.HasValue) { resultTemp += shoppingCartShipping.Value; } resultTemp = _workingCurrency.RoundIfEnabledFor(resultTemp + paymentFeeWithoutTax + shoppingCartTax); // Order total discount. var(discountAmount, appliedDiscount) = await GetDiscountAmountAsync(resultTemp, DiscountType.AssignedToOrderTotal, customer); // Subtotal with discount. if (resultTemp < discountAmount) { discountAmount = resultTemp; } // Reduce subtotal. resultTemp = _workingCurrency.RoundIfEnabledFor(Math.Max(resultTemp - discountAmount, decimal.Zero)); // Applied gift cards. var appliedGiftCards = new List <AppliedGiftCard>(); if (!cart.IncludesMatchingItems(x => x.IsRecurring)) { // TODO: (ms) (core) Gift card usage in OrderCalculationService needs to be tested extensively as the gift card code has been fundamentally changed. var giftCards = await _giftCardService.GetValidGiftCardsAsync(store.Id, customer); foreach (var gc in giftCards) { if (resultTemp > decimal.Zero) { var usableAmount = resultTemp > gc.UsableAmount.Amount ? gc.UsableAmount.Amount : resultTemp; // Reduce subtotal. resultTemp -= usableAmount; appliedGiftCards.Add(new() { GiftCard = gc.GiftCard, UsableAmount = new(usableAmount, _primaryCurrency) }); } } } // Reward points. var redeemedRewardPoints = 0; var redeemedRewardPointsAmount = decimal.Zero; if (_rewardPointsSettings.Enabled && includeRewardPoints && resultTemp > decimal.Zero && customer != null && customer.GenericAttributes.UseRewardPointsDuringCheckout) { var rewardPointsBalance = customer.GetRewardPointsBalance(); var rewardPointsBalanceAmount = ConvertRewardPointsToAmountCore(rewardPointsBalance); if (resultTemp > rewardPointsBalanceAmount) { redeemedRewardPointsAmount = rewardPointsBalanceAmount; redeemedRewardPoints = rewardPointsBalance; } else { redeemedRewardPointsAmount = resultTemp; redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount); } } resultTemp = _workingCurrency.RoundIfEnabledFor(Math.Max(resultTemp, decimal.Zero)); // Return null if we have errors: decimal?orderTotal = shoppingCartShipping.HasValue ? resultTemp : null; var orderTotalConverted = orderTotal; var appliedCreditBalance = decimal.Zero; var toNearestRounding = decimal.Zero; var toNearestRoundingConverted = decimal.Zero; if (orderTotal.HasValue) { orderTotal = orderTotal.Value - redeemedRewardPointsAmount; // Credit balance. if (includeCreditBalance && customer != null && orderTotal > decimal.Zero) { var creditBalance = customer.GenericAttributes.UseCreditBalanceDuringCheckout; if (creditBalance > decimal.Zero) { if (creditBalance > orderTotal) { // Normalize used amount. appliedCreditBalance = orderTotal.Value; customer.GenericAttributes.UseCreditBalanceDuringCheckout = orderTotal.Value; await _db.SaveChangesAsync(); } else { appliedCreditBalance = creditBalance; } } } orderTotal = _workingCurrency.RoundIfEnabledFor(orderTotal.Value - appliedCreditBalance); orderTotalConverted = _currencyService.ConvertToWorkingCurrency(orderTotal.Value).Amount; // Round order total to nearest (cash rounding). if (_workingCurrency.RoundOrderTotalEnabled && paymentMethodSystemName.HasValue()) { var paymentMethod = await _db.PaymentMethods.AsNoTracking().FirstOrDefaultAsync(x => x.PaymentMethodSystemName == paymentMethodSystemName); if (paymentMethod?.RoundOrderTotalEnabled ?? false) { orderTotal = _workingCurrency.RoundToNearest(orderTotal.Value, out toNearestRounding); orderTotalConverted = _workingCurrency.RoundToNearest(orderTotalConverted.Value, out toNearestRoundingConverted); } } } var result = new ShoppingCartTotal { Total = orderTotal.HasValue ? new(orderTotal.Value, _primaryCurrency) : null, ToNearestRounding = new(toNearestRounding, _primaryCurrency), DiscountAmount = new(discountAmount, _primaryCurrency), AppliedDiscount = appliedDiscount, RedeemedRewardPoints = redeemedRewardPoints, RedeemedRewardPointsAmount = new(redeemedRewardPointsAmount, _primaryCurrency), CreditBalance = new(appliedCreditBalance, _primaryCurrency), AppliedGiftCards = appliedGiftCards, ConvertedAmount = new ShoppingCartTotal.ConvertedAmounts { Total = orderTotalConverted.HasValue ? new(orderTotalConverted.Value, _workingCurrency) : null, ToNearestRounding = new(toNearestRoundingConverted, _workingCurrency) } }; return(result); }