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); }
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); }