diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index f850f890c..b3744f3af 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -727,6 +727,18 @@ namespace BTCPayServer.Tests var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default) .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); + // We don't have any default currencies, so this should be failing + Assert.Null(GetRatesResult?.Data); + + var store = acc.GetController(); + var ratesVM = (RatesViewModel)(Assert.IsType(store.Rates(acc.StoreId)).Model); + ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD"; + store.Rates(ratesVM).Wait(); + store = acc.GetController(); + rateController = acc.GetController(); + GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default) + .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); + // Now we should have a result Assert.NotNull(GetRatesResult); Assert.NotNull(GetRatesResult.Data); Assert.Equal(2, GetRatesResult.Data.Length); @@ -1006,7 +1018,7 @@ namespace BTCPayServer.Tests private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD") { var storeController = user.GetController(); - var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model; + var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model; vm.PreferredExchange = exchange; storeController.Rates(vm).Wait(); var invoice2 = user.BitPay.CreateInvoice(new Invoice() @@ -1044,7 +1056,7 @@ namespace BTCPayServer.Tests Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice); var storeController = user.GetController(); - var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model; + var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model; Assert.Equal(0.0, vm.Spread); vm.Spread = 40; storeController.Rates(vm).Wait(); @@ -1143,7 +1155,7 @@ namespace BTCPayServer.Tests user.RegisterDerivationScheme("BTC"); var store = user.GetController(); - var rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); + var rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); Assert.False(rateVm.ShowScripting); Assert.Equal("coinaverage", rateVm.PreferredExchange); Assert.Equal(0.0, rateVm.Spread); @@ -1151,7 +1163,7 @@ namespace BTCPayServer.Tests rateVm.PreferredExchange = "bitflyer"; Assert.IsType(store.Rates(rateVm, "Save").Result); - rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); Assert.Equal("bitflyer", rateVm.PreferredExchange); rateVm.ScriptTest = "BTC_JPY,BTC_CAD"; @@ -1168,7 +1180,8 @@ namespace BTCPayServer.Tests Assert.IsType(store.ShowRateRulesPost(true).Result); Assert.IsType(store.Rates(rateVm, "Save").Result); store = user.GetController(); - rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); + Assert.Equal(rateVm.StoreId, user.StoreId); Assert.Equal(rateVm.DefaultScript, rateVm.Script); Assert.True(rateVm.ShowScripting); rateVm.ScriptTest = "BTC_JPY"; @@ -1185,7 +1198,7 @@ namespace BTCPayServer.Tests Assert.True(rateVm.TestRateRules.All(t => !t.Error)); Assert.IsType(store.Rates(rateVm, "Save").Result); store = user.GetController(); - rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); Assert.Equal(50, rateVm.Spread); Assert.True(rateVm.ShowScripting); Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase); diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index c5f0a146c..e235c097a 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -91,7 +91,6 @@ namespace BTCPayServer.Controllers [BitpayAPIConstraint] public async Task GetRates(string currencyPairs, string storeId, CancellationToken cancellationToken) { - storeId = await GetStoreId(storeId); var result = await GetRates2(currencyPairs, storeId, cancellationToken); var rates = (result as JsonResult)?.Value as Rate[]; if (rates == null) @@ -140,15 +139,10 @@ namespace BTCPayServer.Controllers if (currencyPairs == null) { - var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider); - var currencyCodes = supportedMethods.Select(method => method.PaymentId.CryptoCode).Distinct(); - var defaultPaymentId = store.GetDefaultPaymentId(_NetworkProvider); - - currencyPairs = BuildCurrencyPairs(currencyCodes, defaultPaymentId.CryptoCode); - + currencyPairs = store.GetStoreBlob().GetDefaultCurrencyPairString(); if (string.IsNullOrEmpty(currencyPairs)) { - var result = Json(new BitpayErrorsModel() { Error = "You need to specify currencyPairs (eg. BTC_USD,LTC_CAD)" }); + var result = Json(new BitpayErrorsModel() { Error = "You need to setup the default currency pairs in 'Store Settings / Rates' or specify 'currencyPairs' query parameter (eg. BTC_USD,LTC_CAD)." }); result.StatusCode = 400; return result; } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 38f18d702..453a03c9d 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -189,24 +189,39 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("{storeId}/rates")] - public IActionResult Rates() + public IActionResult Rates(string storeId) { var storeBlob = StoreData.GetStoreBlob(); var vm = new RatesViewModel(); vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName); vm.Spread = (double)(storeBlob.Spread * 100m); + vm.StoreId = storeId; vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString(); vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString(); vm.AvailableExchanges = GetSupportedExchanges(); + vm.DefaultCurrencyPairs = storeBlob.GetDefaultCurrencyPairString(); vm.ShowScripting = storeBlob.RateScripting; return View(vm); } [HttpPost] [Route("{storeId}/rates")] - public async Task Rates(RatesViewModel model, string command = null, CancellationToken cancellationToken = default) + public async Task Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default) { model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange); + model.StoreId = storeId ?? model.StoreId; + CurrencyPair[] currencyPairs = null; + try + { + currencyPairs = model.DefaultCurrencyPairs? + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(p => CurrencyPair.Parse(p)) + .ToArray(); + } + catch + { + ModelState.AddModelError(nameof(model.DefaultCurrencyPairs), "Invalid currency pairs (should be for example: BTC_USD,BTC_CAD,BTC_JPY)"); + } if (!ModelState.IsValid) { return View(model); @@ -220,7 +235,7 @@ namespace BTCPayServer.Controllers blob.PreferredExchange = model.PreferredExchange; blob.Spread = (decimal)model.Spread / 100.0m; - + blob.DefaultCurrencyPairs = currencyPairs; if (!model.ShowScripting) { if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase)) diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 434007a36..485f1499e 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -307,6 +307,25 @@ namespace BTCPayServer.Data public bool RequiresRefundEmail { get; set; } + CurrencyPair[] _DefaultCurrencyPairs; + [JsonProperty("defaultCurrencyPairs", ItemConverterType = typeof(CurrencyPairJsonConverter))] + public CurrencyPair[] DefaultCurrencyPairs + { + get + { + return _DefaultCurrencyPairs ?? Array.Empty(); + } + set + { + _DefaultCurrencyPairs = value; + } + } + + public string GetDefaultCurrencyPairString() + { + return string.Join(',', DefaultCurrencyPairs.Select(c => c.ToString())); + } + public string DefaultLang { get; set; } [DefaultValue(60)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] diff --git a/BTCPayServer/JsonConverters/CurrencyPairJsonConverter.cs b/BTCPayServer/JsonConverters/CurrencyPairJsonConverter.cs new file mode 100644 index 000000000..2c1c9ead1 --- /dev/null +++ b/BTCPayServer/JsonConverters/CurrencyPairJsonConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using NBitcoin.JsonConverters; +using BTCPayServer.Rating; + +namespace BTCPayServer.JsonConverters +{ + public class CurrencyPairJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return typeof(CurrencyValue).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + return reader.TokenType == JsonToken.Null ? null : + CurrencyPair.TryParse((string)reader.Value, out var result) ? result : + throw new JsonObjectException("Invalid currency pair", reader); + } + catch (InvalidCastException) + { + throw new JsonObjectException("Invalid currency pair", reader); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value != null) + writer.WriteValue(value.ToString()); + } + } +} diff --git a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs index a392d8a38..2aa1f423a 100644 --- a/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/RatesViewModel.cs @@ -42,6 +42,8 @@ namespace BTCPayServer.Models.StoreViewModels public string Script { get; set; } public string DefaultScript { get; set; } public string ScriptTest { get; set; } + public string DefaultCurrencyPairs { get; set; } + public string StoreId { get; set; } public CoinAverageExchange[] AvailableExchanges { get; set; } [Display(Name = "Add a spread on exchange rate of ... %")] diff --git a/BTCPayServer/Views/Stores/Rates.cshtml b/BTCPayServer/Views/Stores/Rates.cshtml index 9606d9529..16c652f20 100644 --- a/BTCPayServer/Views/Stores/Rates.cshtml +++ b/BTCPayServer/Views/Stores/Rates.cshtml @@ -15,14 +15,14 @@
- @if(Model.ShowScripting) + @if (Model.ShowScripting) {
Scripting
Rate script allows you to express precisely how you want to calculate rates for currency pairs.

Supported exchanges are: - @for(int i = 0; i < Model.AvailableExchanges.Length; i++) + @for (int i = 0; i < Model.AvailableExchanges.Length; i++) { @Model.AvailableExchanges[i].Name@(i == Model.AvailableExchanges.Length - 1 ? "" : ",") } @@ -30,16 +30,16 @@

Click here for more information

} - @if(Model.TestRateRules != null) + @if (Model.TestRateRules != null) {
Test results:
- @foreach(var result in Model.TestRateRules) + @foreach (var result in Model.TestRateRules) { - @if(result.Error) + @if (result.Error) { } @@ -54,7 +54,7 @@
@result.CurrencyPair
} - @if(Model.ShowScripting) + @if (Model.ShowScripting) {

@@ -157,6 +157,14 @@

+
+
Default currency pairs
+ You can query those pairs via REST by querying this link without the need to specify currencyPairs. +
+ +
+ +