using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using BTCPayServer.Rating; using NBitcoin; using Newtonsoft.Json; namespace BTCPayServer.Services.Rates { public class CurrencyData { public string Name { get; set; } public string Code { get; set; } public int Divisibility { get; set; } public string Symbol { get; set; } public bool Crypto { get; set; } } public class CurrencyNameTable { public static CurrencyNameTable Instance = new CurrencyNameTable(); public CurrencyNameTable() { _Currencies = LoadCurrency().ToDictionary(k => k.Code); } static readonly Dictionary _CurrencyProviders = new Dictionary(); public string FormatCurrency(string price, string currency) { return FormatCurrency(decimal.Parse(price, CultureInfo.InvariantCulture), currency); } public string FormatCurrency(decimal price, string currency) { return price.ToString("C", GetCurrencyProvider(currency)); } public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback) { var data = GetCurrencyProvider(currency); if (data is NumberFormatInfo nfi) return nfi; if (data is CultureInfo ci) return ci.NumberFormat; if (!useFallback) return null; return CreateFallbackCurrencyFormatInfo(currency); } private NumberFormatInfo CreateFallbackCurrencyFormatInfo(string currency) { var usd = GetNumberFormatInfo("USD", false); var currencyInfo = (NumberFormatInfo)usd.Clone(); currencyInfo.CurrencySymbol = currency; return currencyInfo; } public NumberFormatInfo GetNumberFormatInfo(string currency) { var curr = GetCurrencyProvider(currency); if (curr is CultureInfo cu) return cu.NumberFormat; if (curr is NumberFormatInfo ni) return ni; return null; } public IFormatProvider GetCurrencyProvider(string currency) { lock (_CurrencyProviders) { if (_CurrencyProviders.Count == 0) { foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => !c.IsNeutralCulture)) { try { _CurrencyProviders.TryAdd(new RegionInfo(culture.LCID).ISOCurrencySymbol, culture); } catch { } } foreach (var curr in _Currencies.Where(pair => pair.Value.Crypto)) { AddCurrency(_CurrencyProviders, curr.Key, curr.Value.Divisibility, curr.Value.Symbol ?? curr.Value.Code); } } return _CurrencyProviders.TryGet(currency.ToUpperInvariant()); } } private void AddCurrency(Dictionary currencyProviders, string code, int divisibility, string symbol) { var culture = new CultureInfo("en-US"); var number = new NumberFormatInfo(); number.CurrencyDecimalDigits = divisibility; number.CurrencySymbol = symbol; number.CurrencyDecimalSeparator = culture.NumberFormat.CurrencyDecimalSeparator; number.CurrencyGroupSeparator = culture.NumberFormat.CurrencyGroupSeparator; number.CurrencyGroupSizes = culture.NumberFormat.CurrencyGroupSizes; number.CurrencyNegativePattern = 8; number.CurrencyPositivePattern = 3; number.NegativeSign = culture.NumberFormat.NegativeSign; currencyProviders.TryAdd(code, number); } /// /// Format a currency like "0.004 $ (USD)", round to significant divisibility /// /// The value /// Currency code /// public string DisplayFormatCurrency(decimal value, string currency) { var provider = GetNumberFormatInfo(currency, true); var currencyData = GetCurrencyData(currency, true); var divisibility = currencyData.Divisibility; value = value.RoundToSignificant(ref divisibility); if (divisibility != provider.CurrencyDecimalDigits) { provider = (NumberFormatInfo)provider.Clone(); provider.CurrencyDecimalDigits = divisibility; } if (currencyData.Crypto) return value.ToString("C", provider); else return value.ToString("C", provider) + $" ({currency})"; } readonly Dictionary _Currencies; static CurrencyData[] LoadCurrency() { var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BTCPayServer.Rating.Currencies.json"); string content = null; using (var reader = new StreamReader(stream, Encoding.UTF8)) { content = reader.ReadToEnd(); } var currencies = JsonConvert.DeserializeObject(content); return currencies; } public IEnumerable Currencies => _Currencies.Values; public CurrencyData GetCurrencyData(string currency, bool useFallback) { ArgumentNullException.ThrowIfNull(currency); CurrencyData result; if (!_Currencies.TryGetValue(currency.ToUpperInvariant(), out result)) { if (useFallback) { var usd = GetCurrencyData("USD", false); result = new CurrencyData() { Code = currency, Crypto = true, Name = currency, Divisibility = usd.Divisibility }; } } return result; } } }