Example #1
0
        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);
        }