diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 176ca3d36..7ae09e3fd 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -265,7 +265,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); - + // Set tolerance to 50% var stores = user.GetController(); var vm = Assert.IsType(Assert.IsType(stores.UpdateStore()).Model); @@ -296,6 +296,22 @@ namespace BTCPayServer.Tests } } + [Fact] + public void RoundupCurrenciesCorrectly() + { + foreach(var test in new[] + { + (0.0005m, "$0.0005 (USD)"), + (0.001m, "$0.001 (USD)"), + (0.01m, "$0.01 (USD)"), + (0.1m, "$0.10 (USD)"), + }) + { + var actual = InvoiceController.FormatCurrency(test.Item1, "USD", new CurrencyNameTable()); + Assert.Equal(test.Item2, actual); + } + } + [Fact] public void CanPayUsingBIP70() { @@ -617,7 +633,7 @@ namespace BTCPayServer.Tests ItemDesc = "Some description", FullNotifications = true }, Facade.Merchant); - + var cashCow = tester.ExplorerNode; var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10); @@ -1411,7 +1427,7 @@ namespace BTCPayServer.Tests { var provider = new BTCPayNetworkProvider(NetworkType.Mainnet); var factory = CreateBTCPayRateFactory(provider); - + foreach (var result in factory .DirectProviders .Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync())) @@ -1423,8 +1439,8 @@ namespace BTCPayServer.Tests Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]); // This check if the currency pair is using right currency pair - Assert.Contains(exchangeRates.ByExchange[result.ExpectedName], - e => ( e.CurrencyPair == new CurrencyPair("BTC", "USD") || + Assert.Contains(exchangeRates.ByExchange[result.ExpectedName], + e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") || e.CurrencyPair == new CurrencyPair("BTC", "EUR") || e.CurrencyPair == new CurrencyPair("BTC", "USDT")) && e.Value > 1.0m // 1BTC will always be more than 1USD @@ -1454,7 +1470,7 @@ namespace BTCPayServer.Tests private static BTCPayRateProviderFactory CreateBTCPayRateFactory(BTCPayNetworkProvider provider) { - return new BTCPayRateProviderFactory(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }, provider, null, new CoinAverageSettings()); + return new BTCPayRateProviderFactory(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }, provider, new CoinAverageSettings()); } [Fact] diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index f2b8b423a..546bf9b68 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -51,7 +51,7 @@ namespace BTCPayServer.Controllers StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }), Id = invoice.Id, Status = invoice.Status, - TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : + TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" : invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" : "low", @@ -61,7 +61,7 @@ namespace BTCPayServer.Controllers MonitoringDate = invoice.MonitoringExpiration, OrderId = invoice.OrderId, BuyerInformation = invoice.BuyerInformation, - Fiat = FormatCurrency((decimal)dto.Price, dto.Currency), + Fiat = FormatCurrency((decimal)dto.Price, dto.Currency, _CurrencyNameTable), NotificationUrl = invoice.NotificationURL, RedirectUrl = invoice.RedirectURL, ProductInformation = invoice.ProductInformation, @@ -291,11 +291,29 @@ namespace BTCPayServer.Controllers private string FormatCurrency(PaymentMethod paymentMethod) { string currency = paymentMethod.ParentEntity.ProductInformation.Currency; - return FormatCurrency(paymentMethod.Rate, currency); + return FormatCurrency(paymentMethod.Rate, currency, _CurrencyNameTable); } - public string FormatCurrency(decimal price, string currency) + public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies) { - return price.ToString("C", _CurrencyNameTable.GetCurrencyProvider(currency)) + $" ({currency})"; + var provider = ((CultureInfo)currencies.GetCurrencyProvider(currency)).NumberFormat; + var currencyData = currencies.GetCurrencyData(currency); + var divisibility = currencyData.Divisibility; + while (true) + { + var rounded = decimal.Round(price, divisibility, MidpointRounding.AwayFromZero); + if ((Math.Abs(rounded - price) / price) < 0.001m) + { + price = rounded; + break; + } + divisibility++; + } + if(divisibility != provider.CurrencyDecimalDigits) + { + provider = (NumberFormatInfo)provider.Clone(); + provider.CurrencyDecimalDigits = divisibility; + } + return price.ToString("C", provider) + $" ({currency})"; } [HttpGet] @@ -430,7 +448,7 @@ namespace BTCPayServer.Controllers var stores = await _StoreRepository.GetStoresByUserId(GetUserId()); model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId); var store = stores.FirstOrDefault(s => s.Id == model.StoreId); - if(store == null) + if (store == null) { ModelState.AddModelError(nameof(model.StoreId), "Store not found"); } diff --git a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs index 00c3e2b1a..381c148dd 100644 --- a/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs +++ b/BTCPayServer/Services/Rates/BTCPayRateProviderFactory.cs @@ -36,7 +36,6 @@ namespace BTCPayServer.Services.Rates } IMemoryCache _Cache; private IOptions _CacheOptions; - CurrencyNameTable _CurrencyTable; public IMemoryCache Cache { get @@ -47,12 +46,10 @@ namespace BTCPayServer.Services.Rates CoinAverageSettings _CoinAverageSettings; public BTCPayRateProviderFactory(IOptions cacheOptions, BTCPayNetworkProvider btcpayNetworkProvider, - CurrencyNameTable currencyTable, CoinAverageSettings coinAverageSettings) { if (cacheOptions == null) throw new ArgumentNullException(nameof(cacheOptions)); - _CurrencyTable = currencyTable; _CoinAverageSettings = coinAverageSettings; _Cache = new MemoryCache(cacheOptions); _CacheOptions = cacheOptions; @@ -90,7 +87,7 @@ namespace BTCPayServer.Services.Rates public CoinAverageExchanges GetSupportedExchanges() { CoinAverageExchanges exchanges = new CoinAverageExchanges(); - foreach(var exchange in _CoinAverageSettings.AvailableExchanges) + foreach (var exchange in _CoinAverageSettings.AvailableExchanges) { exchanges.Add(exchange.Value); } @@ -180,13 +177,6 @@ namespace BTCPayServer.Services.Rates } rateRule.Reevaluate(); result.Value = rateRule.Value; - - var currencyData = _CurrencyTable?.GetCurrencyData(rateRule.CurrencyPair.Right); - if(currencyData != null && result.Value.HasValue) - { - result.Value = decimal.Round(result.Value.Value, currencyData.Divisibility, MidpointRounding.AwayFromZero); - } - result.Errors = rateRule.Errors; result.EvaluatedRule = rateRule.ToString(true); result.Rule = rateRule.ToString(false);