diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index b3c8d9bfe..837559f0f 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -35,6 +35,7 @@ using BTCPayServer.Services.Apps; using BTCPayServer.Services.Stores; using System.Net.Http; using System.Text; +using BTCPayServer.Models; using BTCPayServer.Rating; using BTCPayServer.Validation; using ExchangeSharp; @@ -731,6 +732,40 @@ namespace BTCPayServer.Tests } } + [Fact] + public void CanGetRates() + { + using (var tester = ServerTester.Create()) + { + tester.Start(); + var acc = tester.NewAccount(); + acc.GrantAccess(); + acc.RegisterDerivationScheme("BTC"); + acc.RegisterDerivationScheme("LTC"); + + var rateController = acc.GetController(); + var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", acc.StoreId) + .GetAwaiter().GetResult()).ToJson()).ToObject>(); + Assert.NotNull(GetBaseCurrencyRatesResult); + Assert.NotNull(GetBaseCurrencyRatesResult.Data); + Assert.Single(GetBaseCurrencyRatesResult.Data); + Assert.Equal("LTC", GetBaseCurrencyRatesResult.Data.First().Code); + + var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId) + .GetAwaiter().GetResult()).ToJson()).ToObject>(); + Assert.NotNull(GetRatesResult); + Assert.NotNull(GetRatesResult.Data); + Assert.Equal(2, GetRatesResult.Data.Length); + + var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", acc.StoreId) + .GetAwaiter().GetResult()).ToJson()).ToObject>(); + + Assert.NotNull(GetCurrencyPairRateResult); + Assert.NotNull(GetCurrencyPairRateResult.Data); + Assert.Equal("LTC", GetCurrencyPairRateResult.Data.Code); + } + } + private void AssertSearchInvoice(TestAccount acc, bool expected, string invoiceId, string filter) { var result = (Models.InvoicingModels.InvoicesModel)((ViewResult)acc.GetController().ListInvoices(filter).Result).Model; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 76ba4f472..378f081c9 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -213,7 +213,7 @@ namespace BTCPayServer.Controllers bool isDefaultCrypto = false; if (paymentMethodIdStr == null) { - paymentMethodIdStr = store.GetDefaultCrypto(); + paymentMethodIdStr = store.GetDefaultCrypto(_NetworkProvider); isDefaultCrypto = true; } diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index 59a8958a9..437bb8f3b 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -31,6 +31,57 @@ namespace BTCPayServer.Controllers _CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable)); } + [Route("rates/{baseCurrency}")] + [HttpGet] + [BitpayAPIConstraint] + public async Task GetBaseCurrencyRates(string baseCurrency, string storeId) + { + storeId = storeId ?? this.HttpContext.GetStoreData()?.Id; + var store = this.HttpContext.GetStoreData(); + if (store == null || store.Id != storeId) + store = await _StoreRepo.FindStore(storeId); + if (store == null) + { + var err = Json(new BitpayErrorsModel() { Error = "Store not found" }); + err.StatusCode = 404; + return err; + } + var currencypairs = ""; + var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider); + + var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode)) + .Select(method => method.PaymentId.CryptoCode).Distinct(); + + + foreach (var currencyCode in currencyCodes) + { + if (!string.IsNullOrEmpty(currencypairs)) + { + currencypairs += ","; + } + currencypairs += baseCurrency + "_ " + currencyCode; + } + var result = await GetRates2(currencypairs, store.Id); + var rates = (result as JsonResult)?.Value as Rate[]; + if (rates == null) + return result; + return Json(new DataWrapper(rates)); + } + + + [Route("rates/{baseCurrency}/{currency}")] + [HttpGet] + [BitpayAPIConstraint] + public async Task GetCurrencyPairRate(string baseCurrency, string currency, string storeId) + { + storeId = storeId ?? this.HttpContext.GetStoreData()?.Id; + var result = await GetRates2($"{baseCurrency}_{currency}", storeId); + var rates = (result as JsonResult)?.Value as Rate[]; + if (rates == null) + return result; + return Json(new DataWrapper(rates.First())); + } + [Route("rates")] [HttpGet] [BitpayAPIConstraint] @@ -44,19 +95,19 @@ namespace BTCPayServer.Controllers return Json(new DataWrapper(rates)); } + [Route("api/rates")] [HttpGet] public async Task GetRates2(string currencyPairs, string storeId) { - if(storeId == null || currencyPairs == null) + if (storeId == null) { - var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings) and currencyPairs (eg. BTC_USD,LTC_CAD)" }); + var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings)" }); result.StatusCode = 400; return result; } - var store = this.HttpContext.GetStoreData(); - if(store == null || store.Id != storeId) + if (store == null || store.Id != storeId) store = await _StoreRepo.FindStore(storeId); if (store == null) { @@ -64,12 +115,40 @@ namespace BTCPayServer.Controllers result.StatusCode = 404; return result; } + + if (currencyPairs == null) + { + currencyPairs = ""; + var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider); + var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode)) + .Select(method => method.PaymentId.CryptoCode).Distinct(); + var defaultCrypto = store.GetDefaultCrypto(_NetworkProvider); + + foreach (var currencyCode in currencyCodes) + { + if (!string.IsNullOrEmpty(currencyPairs)) + { + currencyPairs += ","; + } + currencyPairs += $"{defaultCrypto}_{currencyCode}"; + } + + + if (string.IsNullOrEmpty(currencyPairs)) + { + var result = Json(new BitpayErrorsModel() { Error = "You need to specify currencyPairs (eg. BTC_USD,LTC_CAD)" }); + result.StatusCode = 400; + return result; + } + } + + var rules = store.GetStoreBlob().GetRateRules(_NetworkProvider); HashSet pairs = new HashSet(); - foreach(var currency in currencyPairs.Split(',')) + foreach (var currency in currencyPairs.Split(',')) { - if(!CurrencyPair.TryParse(currency, out var pair)) + if (!CurrencyPair.TryParse(currency, out var pair)) { var result = Json(new BitpayErrorsModel() { Error = $"Currency pair {currency} uncorrectly formatted" }); result.StatusCode = 400; @@ -81,6 +160,7 @@ namespace BTCPayServer.Controllers var fetching = _RateProviderFactory.FetchRates(pairs, rules); await Task.WhenAll(fetching.Select(f => f.Value).ToArray()); return Json(pairs + .AsParallel() .Select(r => (Pair: r, Value: fetching[r].GetAwaiter().GetResult().BidAsk?.Bid)) .Where(r => r.Value.HasValue) .Select(r => diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 4a61de0ff..8ae212e46 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -317,7 +317,7 @@ namespace BTCPayServer.Controllers { var storeBlob = StoreData.GetStoreBlob(); var vm = new CheckoutExperienceViewModel(); - vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto()); + vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto(_NetworkProvider)); vm.SetLanguages(_LangService, storeBlob.DefaultLang); vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? ""; vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? ""; @@ -352,7 +352,7 @@ namespace BTCPayServer.Controllers } bool needUpdate = false; var blob = StoreData.GetStoreBlob(); - if (StoreData.GetDefaultCrypto() != model.DefaultCryptoCurrency) + if (StoreData.GetDefaultCrypto(_NetworkProvider) != model.DefaultCryptoCurrency) { needUpdate = true; StoreData.SetDefaultCrypto(model.DefaultCryptoCurrency); diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 2aff5f446..5fcb772ac 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -197,9 +197,9 @@ namespace BTCPayServer.Data public IEnumerable APIKeys { get; set; } #pragma warning disable CS0618 - public string GetDefaultCrypto() + public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null) { - return DefaultCrypto ?? "BTC"; + return DefaultCrypto ?? (networkProvider == null? "BTC" : GetSupportedPaymentMethods(networkProvider).First().PaymentId.CryptoCode); } public void SetDefaultCrypto(string defaultCryptoCurrency) { diff --git a/BTCPayServer/DerivationStrategy.cs b/BTCPayServer/DerivationStrategy.cs index 88b8da710..463cb4a75 100644 --- a/BTCPayServer/DerivationStrategy.cs +++ b/BTCPayServer/DerivationStrategy.cs @@ -32,7 +32,7 @@ namespace BTCPayServer public BTCPayNetwork Network { get { return this._Network; } } - public DerivationStrategyBase DerivationStrategyBase { get { return this._DerivationStrategy; } } + public DerivationStrategyBase DerivationStrategyBase => this._DerivationStrategy; public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, PaymentTypes.BTCLike); diff --git a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs index 09b416499..b0444567c 100644 --- a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs +++ b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs @@ -202,7 +202,8 @@ namespace BTCPayServer.HostedServices PaymentSubtotals = dto.PaymentSubtotals, PaymentTotals = dto.PaymentTotals, AmountPaid = dto.AmountPaid, - ExchangeRates = dto.ExchangeRates + ExchangeRates = dto.ExchangeRates, + }; // We keep backward compatibility with bitpay by passing BTC info to the notification diff --git a/BTCPayServer/Hosting/BTCpayMiddleware.cs b/BTCPayServer/Hosting/BTCpayMiddleware.cs index a74c8d723..a6808b37b 100644 --- a/BTCPayServer/Hosting/BTCpayMiddleware.cs +++ b/BTCPayServer/Hosting/BTCpayMiddleware.cs @@ -100,7 +100,7 @@ namespace BTCPayServer.Hosting (isJson || httpContext.Request.Query.ContainsKey("token"))) return true; - if (path.Equals("/rates", StringComparison.OrdinalIgnoreCase) && + if (path.StartsWith("/rates", StringComparison.OrdinalIgnoreCase) && httpContext.Request.Method == "GET") return true;