Beispiel #1
0
        private async Task <decimal> GetPaymentMethodAdditionalFeeTaxAsync(
            SortedDictionary <decimal, decimal> taxRates,
            TaxTotalRequest taxTotalRequest
            )
        {
            var paymentMethodAdditionalFeeTax = decimal.Zero;

            if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                var paymentMethodSystemName = taxTotalRequest.Customer != null
                    ? await _genericAttributeService
                                              .GetAttributeAsync <string>(taxTotalRequest.Customer, NopCustomerDefaults.SelectedPaymentMethodAttribute, taxTotalRequest.StoreId)
                    : string.Empty;

                var paymentMethodAdditionalFee = await _paymentService
                                                 .GetAdditionalHandlingFeeAsync(taxTotalRequest.ShoppingCart, paymentMethodSystemName);

                var(paymentMethodAdditionalFeeExclTax, _) = await _taxService
                                                            .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, false, taxTotalRequest.Customer);

                var(paymentMethodAdditionalFeeInclTax, taxRate) = await _taxService
                                                                  .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, true, taxTotalRequest.Customer);

                paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax;
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax;
                    }
                }
            }
            return(paymentMethodAdditionalFeeTax);
        }
Beispiel #2
0
        public virtual async Task <ShoppingCartTotal> GetShoppingCartTotalAsync(
            IList <OrganizedShoppingCartItem> cart,
            bool includeRewardPoints         = true,
            bool includePaymentAdditionalFee = 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 subTotal = await GetShoppingCartSubTotalAsync(cart, false);

            // Subtotal with discount.
            var subtotalBase = subTotal.SubTotalWithDiscount;

            // Shipping without tax.
            Money?shoppingCartShipping = await GetShoppingCartShippingTotalAsync(cart, false);

            // Payment method additional fee without tax.
            var paymentFeeWithoutTax = new Money(_primaryCurrency);

            if (includePaymentAdditionalFee && paymentMethodSystemName.HasValue())
            {
                var provider   = _providerManager.GetProvider <IPaymentMethod>(paymentMethodSystemName);
                var paymentFee = await provider?.Value?.GetAdditionalHandlingFeeAsync(cart);

                (paymentFeeWithoutTax, _) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentFee, false, customer : customer);
            }

            // Tax.
            var(shoppingCartTax, _) = await GetTaxTotalAsync(cart, includePaymentAdditionalFee);

            // Order total.
            var resultTemp = subtotalBase;

            if (shoppingCartShipping.HasValue)
            {
                resultTemp += shoppingCartShipping.Value;
            }

            resultTemp += paymentFeeWithoutTax;
            resultTemp += shoppingCartTax;

            // Round:
            resultTemp = _primaryCurrency.AsMoney(resultTemp.Amount);

            // Order total discount.
            var(discountAmount, appliedDiscount) = await GetDiscountAmountAsync(resultTemp, DiscountType.AssignedToOrderTotal, customer);

            // Subtotal with discount.
            if (resultTemp < discountAmount)
            {
                discountAmount = resultTemp;
            }

            // Reduce subtotal.
            resultTemp = _primaryCurrency.AsMoney((resultTemp - discountAmount).Amount, true, true);

            // 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 ? gc.UsableAmount : resultTemp;

                        // Reduce subtotal.
                        resultTemp -= usableAmount;

                        appliedGiftCards.Add(new()
                        {
                            GiftCard     = gc.GiftCard,
                            UsableAmount = usableAmount
                        });
                    }
                }
            }

            // Reward points.
            var redeemedRewardPoints       = 0;
            var redeemedRewardPointsAmount = new Money(_primaryCurrency);

            if (_rewardPointsSettings.Enabled && includeRewardPoints && resultTemp > decimal.Zero &&
                customer != null && customer.GenericAttributes.UseRewardPointsDuringCheckout)
            {
                var rewardPointsBalance       = customer.GetRewardPointsBalance();
                var rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance);

                if (resultTemp > rewardPointsBalanceAmount)
                {
                    redeemedRewardPointsAmount = rewardPointsBalanceAmount;
                    redeemedRewardPoints       = rewardPointsBalance;
                }
                else
                {
                    redeemedRewardPointsAmount = resultTemp;
                    redeemedRewardPoints       = ConvertAmountToRewardPoints(redeemedRewardPointsAmount);
                }
            }

            resultTemp = _primaryCurrency.AsMoney(resultTemp.Amount, true, true);

            // Return null if we have errors:
            Money?orderTotal                 = shoppingCartShipping.HasValue ? resultTemp : null;
            var   orderTotalConverted        = orderTotal;
            var   appliedCreditBalance       = new Money(_primaryCurrency);
            var   toNearestRounding          = new Money(_primaryCurrency);
            var   toNearestRoundingConverted = new Money(_primaryCurrency);

            if (orderTotal.HasValue)
            {
                orderTotal = orderTotal.Value - redeemedRewardPointsAmount;

                // Credit balance.
                if (includeCreditBalance && customer != null && orderTotal > decimal.Zero)
                {
                    var creditBalance = _primaryCurrency.AsMoney(customer.GenericAttributes.UseCreditBalanceDuringCheckout, false);
                    if (creditBalance > decimal.Zero)
                    {
                        if (creditBalance > orderTotal)
                        {
                            // Normalize used amount.
                            appliedCreditBalance = orderTotal.Value;

                            customer.GenericAttributes.UseCreditBalanceDuringCheckout = orderTotal.Value.Amount;
                            await _db.SaveChangesAsync();
                        }
                        else
                        {
                            appliedCreditBalance = creditBalance;
                        }
                    }
                }

                orderTotal          = _primaryCurrency.AsMoney(orderTotal.Value.Amount - appliedCreditBalance.Amount);
                orderTotalConverted = _currencyService.ConvertFromPrimaryStoreCurrency(orderTotal.Value, store);

                // Round order total to nearest (cash rounding).
                if (_primaryCurrency.RoundOrderTotalEnabled && paymentMethodSystemName.HasValue())
                {
                    var paymentMethod = await _db.PaymentMethods.AsNoTracking().FirstOrDefaultAsync(x => x.PaymentMethodSystemName == paymentMethodSystemName);

                    if (paymentMethod?.RoundOrderTotalEnabled ?? false)
                    {
                        orderTotal          = _primaryCurrency.RoundToNearest(orderTotal.Value, out toNearestRounding);
                        orderTotalConverted = _primaryCurrency.RoundToNearest(orderTotalConverted.Value, out toNearestRoundingConverted);
                    }
                }
            }

            var result = new ShoppingCartTotal(orderTotal)
            {
                ToNearestRounding          = toNearestRounding,
                DiscountAmount             = discountAmount,
                AppliedDiscount            = appliedDiscount,
                RedeemedRewardPoints       = redeemedRewardPoints,
                RedeemedRewardPointsAmount = redeemedRewardPointsAmount,
                CreditBalance    = appliedCreditBalance,
                AppliedGiftCards = appliedGiftCards,
                ConvertedAmount  = new ShoppingCartTotal.ConvertedAmounts
                {
                    Total             = orderTotalConverted,
                    ToNearestRounding = toNearestRoundingConverted
                }
            };

            return(result);
        }
        /// <summary>
        /// Gets tax total
        /// </summary>
        /// <param name="taxTotalRequest">Tax total request</param>
        /// <returns>Tax total</returns>
        public async Task <TaxTotalResult> GetTaxTotalAsync(TaxTotalRequest taxTotalRequest)
        {
            var taxRates = new SortedDictionary <decimal, decimal>();
            var cart     = taxTotalRequest.ShoppingCart;
            var customer = taxTotalRequest.Customer;
            var storeId  = taxTotalRequest.StoreId;
            var usePaymentMethodAdditionalFee = taxTotalRequest.UsePaymentMethodAdditionalFee;

            var paymentMethodSystemName = string.Empty;

            if (customer != null)
            {
                paymentMethodSystemName = await _genericAttributeService.GetAttributeAsync <string>(customer,
                                                                                                    NopCustomerDefaults.SelectedPaymentMethodAttribute, storeId);
            }

            //order sub total (items + checkout attributes)
            var subTotalTaxTotal = decimal.Zero;

            var(_, _, _, _, orderSubTotalTaxRates) = await _orderTotalCalculationService.GetShoppingCartSubTotalAsync(cart, false);

            foreach (var kvp in orderSubTotalTaxRates)
            {
                var taxRate  = kvp.Key;
                var taxValue = kvp.Value;
                subTotalTaxTotal += taxValue;

                if (taxRate <= decimal.Zero || taxValue <= decimal.Zero)
                {
                    continue;
                }

                if (!taxRates.ContainsKey(taxRate))
                {
                    taxRates.Add(taxRate, taxValue);
                }
                else
                {
                    taxRates[taxRate] = taxRates[taxRate] + taxValue;
                }
            }

            //shipping
            var shippingTax = decimal.Zero;

            if (_taxSettings.ShippingIsTaxable)
            {
                var(shippingExclTax, _, _) = await _orderTotalCalculationService.GetShoppingCartShippingTotalAsync(cart, false);

                var(shippingInclTax, taxRate, _) = await _orderTotalCalculationService.GetShoppingCartShippingTotalAsync(cart, true);

                if (shippingExclTax.HasValue && shippingInclTax.HasValue)
                {
                    shippingTax = shippingInclTax.Value - shippingExclTax.Value;
                    //ensure that tax is equal or greater than zero
                    if (shippingTax < decimal.Zero)
                    {
                        shippingTax = decimal.Zero;
                    }

                    //tax rates
                    if (taxRate > decimal.Zero && shippingTax > decimal.Zero)
                    {
                        if (!taxRates.ContainsKey(taxRate))
                        {
                            taxRates.Add(taxRate, shippingTax);
                        }
                        else
                        {
                            taxRates[taxRate] = taxRates[taxRate] + shippingTax;
                        }
                    }
                }
            }

            //payment method additional fee
            var paymentMethodAdditionalFeeTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFeeAsync(cart, paymentMethodSystemName);

                var(paymentMethodAdditionalFeeExclTax, _) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, false, customer);

                var(paymentMethodAdditionalFeeInclTax, taxRate) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, true, customer);

                paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax;
                //ensure that tax is equal or greater than zero
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                //tax rates
                if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax;
                    }
                }
            }

            //add at least one tax rate (0%)
            if (!taxRates.Any())
            {
                taxRates.Add(decimal.Zero, decimal.Zero);
            }

            //summarize taxes
            var taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax;

            //ensure that tax is equal or greater than zero
            if (taxTotal < decimal.Zero)
            {
                taxTotal = decimal.Zero;
            }

            return(new TaxTotalResult {
                TaxTotal = taxTotal, TaxRates = taxRates
            });
        }
        /// <summary>
        /// Gets tax total
        /// </summary>
        /// <param name="taxTotalRequest">Tax total request</param>
        /// <returns>
        /// A task that represents the asynchronous operation
        /// The task result contains the ax total
        /// </returns>
        public async Task <TaxTotalResult> GetTaxTotalAsync(TaxTotalRequest taxTotalRequest)
        {
            if (_httpContextAccessor.HttpContext.Items.TryGetValue("nop.TaxTotal", out var result) &&
                result is (TaxTotalResult taxTotalResult, decimal paymentTax))
            {
                //short-circuit to avoid circular reference when calculating payment method additional fee during the checkout process
                if (!taxTotalRequest.UsePaymentMethodAdditionalFee)
                {
                    return new TaxTotalResult {
                               TaxTotal = taxTotalResult.TaxTotal - paymentTax
                    }
                }
                ;

                return(taxTotalResult);
            }

            var taxRates = new SortedDictionary <decimal, decimal>();
            var taxTotal = decimal.Zero;

            //order sub total (items + checkout attributes)
            var(_, _, _, _, orderSubTotalTaxRates) = await _orderTotalCalculationService
                                                     .GetShoppingCartSubTotalAsync(taxTotalRequest.ShoppingCart, false);

            var subTotalTaxTotal = decimal.Zero;

            foreach (var kvp in orderSubTotalTaxRates)
            {
                var taxRate  = kvp.Key;
                var taxValue = kvp.Value;
                subTotalTaxTotal += taxValue;

                if (taxRate > decimal.Zero && taxValue > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, taxValue);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + taxValue;
                    }
                }
            }
            taxTotal += subTotalTaxTotal;

            //shipping
            var shippingTax = decimal.Zero;

            if (_taxSettings.ShippingIsTaxable)
            {
                var(shippingExclTax, _, _) = await _orderTotalCalculationService
                                             .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, false);

                var(shippingInclTax, taxRate, _) = await _orderTotalCalculationService
                                                   .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, true);

                if (shippingExclTax.HasValue && shippingInclTax.HasValue)
                {
                    shippingTax = shippingInclTax.Value - shippingExclTax.Value;

                    if (shippingTax < decimal.Zero)
                    {
                        shippingTax = decimal.Zero;
                    }

                    if (taxRate > decimal.Zero && shippingTax > decimal.Zero)
                    {
                        if (!taxRates.ContainsKey(taxRate))
                        {
                            taxRates.Add(taxRate, shippingTax);
                        }
                        else
                        {
                            taxRates[taxRate] = taxRates[taxRate] + shippingTax;
                        }
                    }
                }
            }
            taxTotal += shippingTax;

            //short-circuit to avoid circular reference when calculating payment method additional fee during the checkout process
            if (!taxTotalRequest.UsePaymentMethodAdditionalFee)
            {
                return new TaxTotalResult {
                           TaxTotal = taxTotal
                }
            }
            ;

            //payment method additional fee
            var paymentMethodAdditionalFeeTax = decimal.Zero;

            if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                var paymentMethodSystemName = taxTotalRequest.Customer != null
                    ? await _genericAttributeService
                                              .GetAttributeAsync <string>(taxTotalRequest.Customer, NopCustomerDefaults.SelectedPaymentMethodAttribute, taxTotalRequest.StoreId)
                    : string.Empty;

                var paymentMethodAdditionalFee = await _paymentService
                                                 .GetAdditionalHandlingFeeAsync(taxTotalRequest.ShoppingCart, paymentMethodSystemName);

                var(paymentMethodAdditionalFeeExclTax, _) = await _taxService
                                                            .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, false, taxTotalRequest.Customer);

                var(paymentMethodAdditionalFeeInclTax, taxRate) = await _taxService
                                                                  .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, true, taxTotalRequest.Customer);

                paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax;
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax;
                    }
                }
            }
            taxTotal += paymentMethodAdditionalFeeTax;

            //add at least one tax rate (0%)
            if (!taxRates.Any())
            {
                taxRates.Add(decimal.Zero, decimal.Zero);
            }

            if (taxTotal < decimal.Zero)
            {
                taxTotal = decimal.Zero;
            }

            taxTotalResult = new TaxTotalResult {
                TaxTotal = taxTotal, TaxRates = taxRates,
            };

            //store values within the scope of the request to avoid duplicate calculations
            _httpContextAccessor.HttpContext.Items.TryAdd("nop.TaxTotal", (taxTotalResult, paymentMethodAdditionalFeeTax));

            return(taxTotalResult);
        }
        /// <summary>
        /// Prepare payment method model
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="filterByCountryId">Filter by country identifier</param>
        /// <returns>
        /// A task that represents the asynchronous operation
        /// The task result contains the payment method model
        /// </returns>
        public virtual async Task <CheckoutPaymentMethodModel> PreparePaymentMethodModelAsync(IList <ShoppingCartItem> cart, int filterByCountryId)
        {
            var model = new CheckoutPaymentMethodModel();

            //reward points
            if (_rewardPointsSettings.Enabled && !await _shoppingCartService.ShoppingCartIsRecurringAsync(cart))
            {
                var rewardPointsBalance = await _rewardPointService.GetRewardPointsBalanceAsync((await _workContext.GetCurrentCustomerAsync()).Id, (await _storeContext.GetCurrentStoreAsync()).Id);

                rewardPointsBalance = _rewardPointService.GetReducedPointsBalance(rewardPointsBalance);

                var rewardPointsAmountBase = await _orderTotalCalculationService.ConvertRewardPointsToAmountAsync(rewardPointsBalance);

                var rewardPointsAmount = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(rewardPointsAmountBase, await _workContext.GetWorkingCurrencyAsync());

                if (rewardPointsAmount > decimal.Zero &&
                    _orderTotalCalculationService.CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance))
                {
                    model.DisplayRewardPoints = true;
                    model.RewardPointsAmount  = await _priceFormatter.FormatPriceAsync(rewardPointsAmount, true, false);

                    model.RewardPointsBalance = rewardPointsBalance;

                    //are points enough to pay for entire order? like if this option (to use them) was selected
                    model.RewardPointsEnoughToPayForOrder = !await _orderProcessingService.IsPaymentWorkflowRequiredAsync(cart, true);
                }
            }

            //filter by country
            var paymentMethods = await(await _paymentPluginManager
                                       .LoadActivePluginsAsyncAsync(await _workContext.GetCurrentCustomerAsync(), (await _storeContext.GetCurrentStoreAsync()).Id, filterByCountryId))
                                 .Where(pm => pm.PaymentMethodType == PaymentMethodType.Standard || pm.PaymentMethodType == PaymentMethodType.Redirection)
                                 .WhereAwait(async pm => !await pm.HidePaymentMethodAsync(cart))
                                 .ToListAsync();

            foreach (var pm in paymentMethods)
            {
                if (await _shoppingCartService.ShoppingCartIsRecurringAsync(cart) && pm.RecurringPaymentType == RecurringPaymentType.NotSupported)
                {
                    continue;
                }

                var pmModel = new CheckoutPaymentMethodModel.PaymentMethodModel
                {
                    Name                    = await _localizationService.GetLocalizedFriendlyNameAsync(pm, (await _workContext.GetWorkingLanguageAsync()).Id),
                    Description             = _paymentSettings.ShowPaymentMethodDescriptions ? await pm.GetPaymentMethodDescriptionAsync() : string.Empty,
                    PaymentMethodSystemName = pm.PluginDescriptor.SystemName,
                    LogoUrl                 = await _paymentPluginManager.GetPluginLogoUrlAsync(pm)
                };
                //payment method additional fee
                var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFeeAsync(cart, pm.PluginDescriptor.SystemName);

                var(rateBase, _) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, await _workContext.GetCurrentCustomerAsync());

                var rate = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(rateBase, await _workContext.GetWorkingCurrencyAsync());

                if (rate > decimal.Zero)
                {
                    pmModel.Fee = await _priceFormatter.FormatPaymentMethodAdditionalFeeAsync(rate, true);
                }

                model.PaymentMethods.Add(pmModel);
            }

            //find a selected (previously) payment method
            var selectedPaymentMethodSystemName = await _genericAttributeService.GetAttributeAsync <string>(await _workContext.GetCurrentCustomerAsync(),
                                                                                                            NopCustomerDefaults.SelectedPaymentMethodAttribute, (await _storeContext.GetCurrentStoreAsync()).Id);

            if (!string.IsNullOrEmpty(selectedPaymentMethodSystemName))
            {
                var paymentMethodToSelect = model.PaymentMethods.ToList()
                                            .Find(pm => pm.PaymentMethodSystemName.Equals(selectedPaymentMethodSystemName, StringComparison.InvariantCultureIgnoreCase));
                if (paymentMethodToSelect != null)
                {
                    paymentMethodToSelect.Selected = true;
                }
            }
            //if no option has been selected, let's do it for the first one
            if (model.PaymentMethods.FirstOrDefault(so => so.Selected) == null)
            {
                var paymentMethodToSelect = model.PaymentMethods.FirstOrDefault();
                if (paymentMethodToSelect != null)
                {
                    paymentMethodToSelect.Selected = true;
                }
            }

            return(model);
        }