mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-13 11:35:51 +01:00
The store owner can define default currency pairs when using rate API without parameter
This commit is contained in:
parent
c6ce676ad3
commit
23f296ef34
7 changed files with 113 additions and 23 deletions
|
@ -727,6 +727,18 @@ namespace BTCPayServer.Tests
|
|||
|
||||
var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
// We don't have any default currencies, so this should be failing
|
||||
Assert.Null(GetRatesResult?.Data);
|
||||
|
||||
var store = acc.GetController<StoresController>();
|
||||
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates(acc.StoreId)).Model);
|
||||
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
|
||||
store.Rates(ratesVM).Wait();
|
||||
store = acc.GetController<StoresController>();
|
||||
rateController = acc.GetController<RateController>();
|
||||
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
// 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<StoresController>();
|
||||
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<StoresController>();
|
||||
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<StoresController>();
|
||||
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(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<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(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<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(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<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
|
||||
Assert.Equal(50, rateVm.Spread);
|
||||
Assert.True(rateVm.ShowScripting);
|
||||
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
|
||||
|
|
|
@ -91,7 +91,6 @@ namespace BTCPayServer.Controllers
|
|||
[BitpayAPIConstraint]
|
||||
public async Task<IActionResult> 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;
|
||||
}
|
||||
|
|
|
@ -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<IActionResult> Rates(RatesViewModel model, string command = null, CancellationToken cancellationToken = default)
|
||||
public async Task<IActionResult> 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))
|
||||
|
|
|
@ -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<CurrencyPair>();
|
||||
}
|
||||
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)]
|
||||
|
|
39
BTCPayServer/JsonConverters/CurrencyPairJsonConverter.cs
Normal file
39
BTCPayServer/JsonConverters/CurrencyPairJsonConverter.cs
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ... %")]
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
@if(Model.ShowScripting)
|
||||
@if (Model.ShowScripting)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Scripting</h5>
|
||||
<span>Rate script allows you to express precisely how you want to calculate rates for currency pairs.</span>
|
||||
<p class="text-muted">
|
||||
<b>Supported exchanges are</b>:
|
||||
@for(int i = 0; i < Model.AvailableExchanges.Length; i++)
|
||||
@for (int i = 0; i < Model.AvailableExchanges.Length; i++)
|
||||
{
|
||||
<a href="@Model.AvailableExchanges[i].Url">@Model.AvailableExchanges[i].Name</a><span>@(i == Model.AvailableExchanges.Length - 1 ? "" : ",")</span>
|
||||
}
|
||||
|
@ -30,16 +30,16 @@
|
|||
<p><a href="#help" data-toggle="collapse"><b>Click here for more information</b></a></p>
|
||||
</div>
|
||||
}
|
||||
@if(Model.TestRateRules != null)
|
||||
@if (Model.TestRateRules != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Test results:</h5>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<tbody>
|
||||
@foreach(var result in Model.TestRateRules)
|
||||
@foreach (var result in Model.TestRateRules)
|
||||
{
|
||||
<tr>
|
||||
@if(result.Error)
|
||||
@if (result.Error)
|
||||
{
|
||||
<th class="small"><span class="fa fa-times" style="color:red;"></span> @result.CurrencyPair</th>
|
||||
}
|
||||
|
@ -54,7 +54,7 @@
|
|||
</table>
|
||||
</div>
|
||||
}
|
||||
@if(Model.ShowScripting)
|
||||
@if (Model.ShowScripting)
|
||||
{
|
||||
<div id="help" class="collapse text-left">
|
||||
<p>
|
||||
|
@ -157,6 +157,14 @@
|
|||
</div>
|
||||
<span asp-validation-for="ScriptTest" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<h5>Default currency pairs</h5>
|
||||
<span>You can query those pairs via REST by querying <a asp-controller="Rate" asp-action="GetRates2" asp-route-storeId="@Model.StoreId" target="_blank">this link</a> without the need to specify currencyPairs.</span>
|
||||
<div class="input-group">
|
||||
<input placeholder="BTC_USD, BTC_CAD" asp-for="DefaultCurrencyPairs" class="form-control" />
|
||||
</div>
|
||||
<span asp-validation-for="DefaultCurrencyPairs" class="text-danger"></span>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save">Save</button>
|
||||
<input type="hidden" asp-for="ShowScripting" />
|
||||
</form>
|
||||
|
|
Loading…
Add table
Reference in a new issue