mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Recommended exchange to be resolved during Invoice Creation (#5976)
* Recommended Exchange Rate Selection during Invoice Creation * Make Recommended exchanges pluginifiable
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -203,7 +203,7 @@ namespace BTCPayServer.Rating
|
||||
return (ExpressionSyntax)CSharpSyntaxTree.ParseText(str, new CSharpParseOptions(LanguageVersion.Default).WithKind(SourceCodeKind.Script)).GetRoot().ChildNodes().First().ChildNodes().First().ChildNodes().First();
|
||||
}
|
||||
|
||||
public static RateRules Combine(RateRules[] rateRules)
|
||||
public static RateRules Combine(IEnumerable<RateRules> rateRules)
|
||||
{
|
||||
var str = string.Join(Environment.NewLine, rateRules.Select(r => r.ToString()));
|
||||
return Parse(str);
|
||||
|
||||
@@ -4284,7 +4284,7 @@ namespace BTCPayServer.Tests
|
||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||
Assert.Equal("X_X = coingecko(X_X);", config.EffectiveScript);
|
||||
|
||||
await AssertValidationError(new[] { "EffectiveScript", "PreferredSource" }, () =>
|
||||
await AssertValidationError(new[] { "EffectiveScript" }, () =>
|
||||
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, EffectiveScript = "BTC_XYZ = 1;" }));
|
||||
|
||||
await AssertValidationError(new[] { "EffectiveScript" }, () =>
|
||||
|
||||
@@ -196,7 +196,7 @@ retry:
|
||||
TestLogs.LogInformation($"Created store {name}");
|
||||
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
||||
var rateSource = new SelectElement(Driver.FindElement(By.Id("PreferredExchange")));
|
||||
Assert.Equal("Kraken (Recommended)", rateSource.SelectedOption.Text);
|
||||
Assert.Equal("Recommendation (Kraken)", rateSource.SelectedOption.Text);
|
||||
rateSource.SelectByText("CoinGecko");
|
||||
Driver.WaitForElement(By.Id("Create")).Click();
|
||||
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Rating;
|
||||
@@ -357,12 +358,13 @@ retry:
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
var provider = CreateDefaultRates(ChainName.Mainnet);
|
||||
var defaultRules = new DefaultRulesCollection(provider.Select(p => p.DefaultRates));
|
||||
var b = new StoreBlob();
|
||||
string[] temporarilyBroken = Array.Empty<string>();
|
||||
foreach (var k in StoreBlob.RecommendedExchanges)
|
||||
foreach (var k in defaultRules.RecommendedExchanges)
|
||||
{
|
||||
b.DefaultCurrency = k.Key;
|
||||
var rules = b.GetDefaultRateRules(provider.Select(p => p.DefaultRates));
|
||||
var rules = b.GetDefaultRateRules(defaultRules);
|
||||
var pairs = new[] { CurrencyPair.Parse($"BTC_{k.Key}") }.ToHashSet();
|
||||
var result = fetcher.FetchRates(pairs, rules, null, default);
|
||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||
@@ -389,11 +391,13 @@ retry:
|
||||
public async Task CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
using var cts = new CancellationTokenSource(60_000);
|
||||
var provider = CreateDefaultRates(ChainName.Mainnet);
|
||||
var provider = CreateDefaultRates(ChainName.Mainnet, exchangeRecommendation: true);
|
||||
var defaultRules = new DefaultRulesCollection(provider.Select(p => p.DefaultRates));
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
var pairs =
|
||||
provider
|
||||
.Where(c => c.CryptoCode is not null)
|
||||
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
|
||||
.ToHashSet();
|
||||
|
||||
@@ -408,7 +412,7 @@ retry:
|
||||
}
|
||||
}
|
||||
|
||||
var rules = new StoreBlob().GetDefaultRateRules(provider.Select(p => p.DefaultRates));
|
||||
var rules = new StoreBlob().GetDefaultRateRules(defaultRules);
|
||||
var result = fetcher.FetchRates(pairs, rules, null, cts.Token);
|
||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||
{
|
||||
@@ -418,13 +422,22 @@ retry:
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(string CryptoCode, DefaultRates DefaultRates)> CreateDefaultRates(ChainName chainName)
|
||||
private IEnumerable<(string CryptoCode, DefaultRules DefaultRates)> CreateDefaultRates(ChainName chainName, bool exchangeRecommendation = false)
|
||||
{
|
||||
var results = new List<(string CryptoCode, DefaultRates DefaultRates)>();
|
||||
var results = new List<(string CryptoCode, DefaultRules DefaultRates)>();
|
||||
var prov = CreateNetworkProvider(chainName);
|
||||
foreach (var network in prov.GetAll())
|
||||
{
|
||||
results.Add((network.CryptoCode, new DefaultRates(network.DefaultRateRules)));
|
||||
results.Add((network.CryptoCode, new DefaultRules(network.DefaultRateRules)));
|
||||
}
|
||||
if (exchangeRecommendation)
|
||||
{
|
||||
ServiceCollection services = new ServiceCollection();
|
||||
BTCPayServerServices.RegisterExchangeRecommendations(services);
|
||||
foreach (var rule in services.BuildServiceProvider().GetRequiredService<IEnumerable<DefaultRules>>())
|
||||
{
|
||||
results.Add((null, rule));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Components.WalletNav
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly UIWalletsController _walletsController;
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly RateFetcher _rateFetcher;
|
||||
|
||||
public WalletNav(
|
||||
@@ -39,14 +39,14 @@ namespace BTCPayServer.Components.WalletNav
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
UIWalletsController walletsController,
|
||||
CurrencyNameTable currencies,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
RateFetcher rateFetcher)
|
||||
{
|
||||
_walletProvider = walletProvider;
|
||||
_handlers = handlers;
|
||||
_walletsController = walletsController;
|
||||
_currencies = currencies;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_rateFetcher = rateFetcher;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace BTCPayServer.Components.WalletNav
|
||||
|
||||
if (defaultCurrency != network.CryptoCode)
|
||||
{
|
||||
var rule = store.GetStoreBlob().GetRateRules(_defaultRates)?.GetRuleFor(new Rating.CurrencyPair(network.CryptoCode, defaultCurrency));
|
||||
var rule = store.GetStoreBlob().GetRateRules(_defaultRules)?.GetRuleFor(new Rating.CurrencyPair(network.CryptoCode, defaultCurrency));
|
||||
var bid = rule is null ? null : (await _rateFetcher.FetchRate(rule, new StoreIdRateContext(walletId.StoreId), HttpContext.RequestAborted)).BidAsk?.Bid;
|
||||
if (bid is decimal b)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
readonly RateFetcher _rateProviderFactory;
|
||||
readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
readonly StoreRepository _storeRepo;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
@@ -43,14 +43,14 @@ namespace BTCPayServer.Controllers
|
||||
StoreRepository storeRepo,
|
||||
InvoiceRepository invoiceRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
PaymentMethodHandlerDictionary handlers)
|
||||
{
|
||||
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
||||
_storeRepo = storeRepo;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
var rules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
||||
var rules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||
var pairs = new HashSet<CurrencyPair>();
|
||||
foreach (var currency in currencyPairs.Split(','))
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
|
||||
public LanguageService LanguageService { get; }
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
||||
PayoutMethodHandlerDictionary payoutHandlers,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
IEnumerable<DefaultRates> defaultRates)
|
||||
DefaultRulesCollection defaultRules)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
@@ -76,7 +76,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_paymentLinkExtensions = paymentLinkExtensions;
|
||||
_payoutHandlers = payoutHandlers;
|
||||
_handlers = handlers;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
|
||||
var rateResult = await _rateProvider.FetchRate(
|
||||
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
|
||||
store.GetStoreBlob().GetRateRules(_defaultRates), new StoreIdRateContext(storeId),
|
||||
store.GetStoreBlob().GetRateRules(_defaultRules), new StoreIdRateContext(storeId),
|
||||
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
@@ -27,16 +27,16 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public class GreenfieldStoreRateConfigurationController : ControllerBase
|
||||
{
|
||||
private readonly RateFetcher _rateProviderFactory;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public GreenfieldStoreRateConfigurationController(
|
||||
RateFetcher rateProviderFactory,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
StoreRepository storeRepository)
|
||||
{
|
||||
_rateProviderFactory = rateProviderFactory;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
return Ok(new StoreRateConfiguration()
|
||||
{
|
||||
EffectiveScript = blob.GetRateRules(_defaultRates, out var preferredExchange).ToString(),
|
||||
EffectiveScript = blob.GetRateRules(_defaultRules, out var preferredExchange).ToString(),
|
||||
Spread = blob.Spread * 100.0m,
|
||||
IsCustomScript = blob.RateScripting,
|
||||
PreferredSource = preferredExchange ? blob.PreferredExchange : null
|
||||
PreferredSource = preferredExchange ? blob.GetPreferredExchange(_defaultRules) : null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return this.CreateValidationError(ModelState);
|
||||
PopulateBlob(configuration, blob);
|
||||
|
||||
var rules = blob.GetRateRules(_defaultRates);
|
||||
var rules = blob.GetRateRules(_defaultRules);
|
||||
|
||||
|
||||
var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, new StoreIdRateContext(data.Id), CancellationToken.None);
|
||||
@@ -156,7 +156,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
if (string.IsNullOrEmpty(configuration.EffectiveScript))
|
||||
{
|
||||
configuration.EffectiveScript = storeBlob.GetDefaultRateRules(_defaultRates).ToString();
|
||||
configuration.EffectiveScript = storeBlob.GetDefaultRateRules(_defaultRules).ToString();
|
||||
}
|
||||
|
||||
if (!RateRules.TryParse(configuration.EffectiveScript, out var r))
|
||||
@@ -182,12 +182,8 @@ $"You can't set the preferredSource if you are using custom scripts");
|
||||
ModelState.AddModelError(nameof(configuration.EffectiveScript),
|
||||
$"You can't set the effectiveScript if you aren't using custom scripts");
|
||||
}
|
||||
if (string.IsNullOrEmpty(configuration.PreferredSource))
|
||||
if (!string.IsNullOrEmpty(configuration.PreferredSource))
|
||||
{
|
||||
ModelState.AddModelError(nameof(configuration.PreferredSource),
|
||||
$"The preferredSource is required if you aren't using custom scripts");
|
||||
}
|
||||
|
||||
configuration.PreferredSource = _rateProviderFactory
|
||||
.RateProviderFactory
|
||||
.AvailableRateProviders
|
||||
@@ -202,6 +198,7 @@ $"The preferredSource is required if you aren't using custom scripts");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PopulateBlob(StoreRateConfiguration configuration, StoreBlob storeBlob)
|
||||
{
|
||||
|
||||
@@ -24,14 +24,14 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public class GreenfieldStoreRatesController : ControllerBase
|
||||
{
|
||||
private readonly RateFetcher _rateProviderFactory;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
|
||||
public GreenfieldStoreRatesController(
|
||||
RateFetcher rateProviderFactory,
|
||||
IEnumerable<DefaultRates> defaultRates)
|
||||
DefaultRulesCollection defaultRules)
|
||||
{
|
||||
_rateProviderFactory = rateProviderFactory;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
|
||||
|
||||
var rules = blob.GetRateRules(_defaultRates);
|
||||
var rules = blob.GetRateRules(_defaultRules);
|
||||
|
||||
|
||||
var rateTasks = _rateProviderFactory.FetchRates(parsedCurrencyPairs, rules, new StoreIdRateContext(data.Id), CancellationToken.None);
|
||||
|
||||
@@ -382,7 +382,7 @@ namespace BTCPayServer.Controllers
|
||||
var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility);
|
||||
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
|
||||
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodCurrency);
|
||||
rules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
||||
rules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||
rateResult = await _RateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, new StoreIdRateContext(store.Id),
|
||||
cancellationToken);
|
||||
@@ -491,7 +491,7 @@ namespace BTCPayServer.Controllers
|
||||
return View("_RefundModal", model);
|
||||
}
|
||||
|
||||
rules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
||||
rules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||
rateResult = await _RateProvider.FetchRate(
|
||||
new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules, new StoreIdRateContext(store.Id),
|
||||
cancellationToken);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace BTCPayServer.Controllers
|
||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly PullPaymentHostedService _paymentHostedService;
|
||||
private readonly LanguageService _languageService;
|
||||
@@ -94,7 +94,7 @@ namespace BTCPayServer.Controllers
|
||||
AppService appService,
|
||||
IFileService fileService,
|
||||
UriResolver uriResolver,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
IAuthorizationService authorizationService,
|
||||
TransactionLinkProviders transactionLinkProviders,
|
||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||
@@ -125,7 +125,7 @@ namespace BTCPayServer.Controllers
|
||||
_viewProvider = viewProvider;
|
||||
_fileService = fileService;
|
||||
_uriResolver = uriResolver;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_appService = appService;
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task FetchRates(InvoiceCreationContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var rateRules = context.StoreBlob.GetRateRules(_defaultRates);
|
||||
var rateRules = context.StoreBlob.GetRateRules(_defaultRules);
|
||||
await context.FetchingRates(_RateProvider, rateRules, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static BTCPayServer.Lightning.Eclair.Models.ChannelResponse;
|
||||
|
||||
namespace BTCPayServer.Controllers;
|
||||
|
||||
@@ -22,17 +23,9 @@ public partial class UIStoresController
|
||||
[HttpGet("{storeId}/rates")]
|
||||
public IActionResult Rates()
|
||||
{
|
||||
var exchanges = GetSupportedExchanges().ToList();
|
||||
var storeBlob = CurrentStore.GetStoreBlob();
|
||||
var vm = new RatesViewModel();
|
||||
vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? storeBlob.GetRecommendedExchange());
|
||||
vm.Spread = (double)(storeBlob.Spread * 100m);
|
||||
vm.StoreId = CurrentStore.Id;
|
||||
vm.Script = storeBlob.GetRateRules(_defaultRates).ToString();
|
||||
vm.DefaultScript = storeBlob.GetDefaultRateRules(_defaultRates).ToString();
|
||||
vm.AvailableExchanges = exchanges;
|
||||
vm.DefaultCurrencyPairs = storeBlob.GetDefaultCurrencyPairString();
|
||||
vm.ShowScripting = storeBlob.RateScripting;
|
||||
FillFromStore(vm, storeBlob);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@@ -48,9 +41,6 @@ public partial class UIStoresController
|
||||
{
|
||||
return RedirectToAction(nameof(ShowRateRules), new { scripting = false, storeId = model.StoreId });
|
||||
}
|
||||
|
||||
var exchanges = GetSupportedExchanges().ToList();
|
||||
model.SetExchangeRates(exchanges, model.PreferredExchange ?? HttpContext.GetStoreData().GetStoreBlob().GetRecommendedExchange());
|
||||
model.StoreId = storeId ?? model.StoreId;
|
||||
CurrencyPair[]? currencyPairs = null;
|
||||
try
|
||||
@@ -70,22 +60,10 @@ public partial class UIStoresController
|
||||
}
|
||||
if (model.PreferredExchange != null)
|
||||
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(model.PreferredExchange))
|
||||
model.PreferredExchange = null;
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
model.DefaultScript = blob.GetDefaultRateRules(_defaultRates).ToString();
|
||||
model.AvailableExchanges = exchanges;
|
||||
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
blob.Spread = (decimal)model.Spread / 100.0m;
|
||||
blob.DefaultCurrencyPairs = currencyPairs;
|
||||
if (!model.ShowScripting)
|
||||
{
|
||||
if (!exchanges.Any(provider => provider.Id.Equals(model.PreferredExchange, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})");
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
RateRules? rules;
|
||||
if (model.ShowScripting)
|
||||
{
|
||||
@@ -94,17 +72,20 @@ public partial class UIStoresController
|
||||
errors ??= [];
|
||||
var errorString = string.Join(", ", errors.ToArray());
|
||||
ModelState.AddModelError(nameof(model.Script), $"Parsing error ({errorString})");
|
||||
FillFromStore(model, blob);
|
||||
return View(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
blob.RateScript = rules.ToString();
|
||||
ModelState.Remove(nameof(model.Script));
|
||||
model.Script = blob.RateScript;
|
||||
}
|
||||
}
|
||||
rules = blob.GetRateRules(_defaultRates);
|
||||
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
blob.Spread = (decimal)model.Spread / 100.0m;
|
||||
blob.DefaultCurrencyPairs = currencyPairs;
|
||||
FillFromStore(model, blob);
|
||||
rules = blob.GetRateRules(_defaultRules);
|
||||
if (command == "Test")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(model.ScriptTest))
|
||||
@@ -142,6 +123,12 @@ public partial class UIStoresController
|
||||
return View(model);
|
||||
}
|
||||
|
||||
if (model.PreferredExchange is not null && !model.AvailableExchanges.Any(a => a.Id == model.PreferredExchange))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
// command == Save
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
@@ -175,16 +162,29 @@ public partial class UIStoresController
|
||||
{
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
blob.RateScripting = scripting;
|
||||
blob.RateScript = blob.GetDefaultRateRules(_defaultRates).ToString();
|
||||
blob.RateScript = blob.GetDefaultRateRules(_defaultRules).ToString();
|
||||
CurrentStore.SetStoreBlob(blob);
|
||||
await _storeRepo.UpdateStore(CurrentStore);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting " + (scripting ? "activated" : "deactivated");
|
||||
return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
private IEnumerable<RateSourceInfo> GetSupportedExchanges()
|
||||
private void FillFromStore(RatesViewModel vm, StoreBlob storeBlob)
|
||||
{
|
||||
return _rateFactory.RateProviderFactory.AvailableRateProviders
|
||||
var sources = _rateFactory.RateProviderFactory.AvailableRateProviders
|
||||
.OrderBy(s => s.DisplayName, StringComparer.OrdinalIgnoreCase);
|
||||
vm.AvailableExchanges = sources;
|
||||
var exchange = storeBlob.GetPreferredExchange(_defaultRules);
|
||||
var chosenSource = sources.First(r => r.Id == exchange);
|
||||
vm.Exchanges = UIUserStoresController.GetExchangesSelectList(_rateFactory, _defaultRules, storeBlob);
|
||||
vm.PreferredExchange = vm.Exchanges.SelectedValue as string;
|
||||
vm.PreferredResolvedExchange = chosenSource.Id;
|
||||
vm.RateSource = chosenSource.Url;
|
||||
vm.Spread = (double)(storeBlob.Spread * 100m);
|
||||
vm.StoreId = CurrentStore.Id;
|
||||
vm.Script = storeBlob.GetRateRules(_defaultRules).ToString();
|
||||
vm.DefaultScript = storeBlob.GetDefaultRateRules(_defaultRules).ToString();
|
||||
vm.DefaultCurrencyPairs = storeBlob.GetDefaultCurrencyPairString();
|
||||
vm.ShowScripting = storeBlob.RateScripting;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public partial class UIStoresController : Controller
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||
IHtmlHelper html,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
EmailSenderFactory emailSenderFactory,
|
||||
WalletFileParsers onChainWalletParsers,
|
||||
UriResolver uriResolver,
|
||||
@@ -85,7 +85,7 @@ public partial class UIStoresController : Controller
|
||||
_settingsRepository = settingsRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_html = html;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_dataProtector = dataProtector.CreateProtector("ConfigProtector");
|
||||
_webhookNotificationManager = webhookNotificationManager;
|
||||
_lightningNetworkOptions = lightningNetworkOptions.Value;
|
||||
@@ -104,7 +104,7 @@ public partial class UIStoresController : Controller
|
||||
private readonly ExplorerClientProvider _explorerProvider;
|
||||
private readonly LanguageService _langService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly PoliciesSettings _policiesSettings;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly AppService _appService;
|
||||
|
||||
@@ -23,17 +23,20 @@ namespace BTCPayServer.Controllers
|
||||
private readonly StoreRepository _repo;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly RateFetcher _rateFactory;
|
||||
public string CreatedStoreId { get; set; }
|
||||
|
||||
public UIUserStoresController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
DefaultRulesCollection defaultRules,
|
||||
StoreRepository storeRepository,
|
||||
RateFetcher rateFactory,
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
_repo = storeRepository;
|
||||
_userManager = userManager;
|
||||
_defaultRules = defaultRules;
|
||||
_rateFactory = rateFactory;
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
@@ -81,7 +84,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var stores = await _repo.GetStoresByUserId(GetUserId());
|
||||
vm.IsFirstStore = !stores.Any();
|
||||
vm.Exchanges = GetExchangesSelectList(vm.PreferredExchange);
|
||||
vm.Exchanges = GetExchangesSelectList(null);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@@ -124,14 +127,19 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private string GetUserId() => _userManager.GetUserId(User);
|
||||
|
||||
private SelectList GetExchangesSelectList(string selected)
|
||||
private SelectList GetExchangesSelectList(StoreBlob storeBlob) => GetExchangesSelectList(_rateFactory, _defaultRules, storeBlob);
|
||||
internal static SelectList GetExchangesSelectList(RateFetcher rateFetcher, DefaultRulesCollection defaultRules, StoreBlob storeBlob)
|
||||
{
|
||||
var exchanges = _rateFactory.RateProviderFactory
|
||||
if (storeBlob is null)
|
||||
storeBlob = new StoreBlob();
|
||||
var defaultExchange = defaultRules.GetRecommendedExchange(storeBlob.DefaultCurrency);
|
||||
var exchanges = rateFetcher.RateProviderFactory
|
||||
.AvailableRateProviders
|
||||
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
exchanges.Insert(0, new (null, "Recommended", ""));
|
||||
var chosen = exchanges.FirstOrDefault(f => f.Id == selected) ?? exchanges.First();
|
||||
var exchange = exchanges.First(e => e.Id == defaultExchange);
|
||||
exchanges.Insert(0, new(null, $"Recommendation ({exchange.DisplayName})", ""));
|
||||
var chosen = exchanges.FirstOrDefault(f => f.Id == storeBlob.PreferredExchange) ?? exchanges.First();
|
||||
return new SelectList(exchanges, nameof(chosen.Id), nameof(chosen.DisplayName), chosen.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly LabelService _labelService;
|
||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||
@@ -100,14 +100,14 @@ namespace BTCPayServer.Controllers
|
||||
IServiceProvider serviceProvider,
|
||||
PullPaymentHostedService pullPaymentHostedService,
|
||||
LabelService labelService,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
PaymentMethodHandlerDictionary handlers,
|
||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||
TransactionLinkProviders transactionLinkProviders)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
_labelService = labelService;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_handlers = handlers;
|
||||
_paymentModelExtensions = paymentModelExtensions;
|
||||
_transactionLinkProviders = transactionLinkProviders;
|
||||
@@ -459,7 +459,7 @@ namespace BTCPayServer.Controllers
|
||||
if (network == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
var storeData = store.GetStoreBlob();
|
||||
var rateRules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
||||
var rateRules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||
rateRules.Spread = 0.0m;
|
||||
var currencyPair = new Rating.CurrencyPair(walletId.CryptoCode, storeData.DefaultCurrency);
|
||||
double.TryParse(defaultAmount, out var amount);
|
||||
|
||||
@@ -102,7 +102,19 @@ namespace BTCPayServer.Data
|
||||
|
||||
public decimal Spread { get; set; } = 0.0m;
|
||||
|
||||
/// <summary>
|
||||
/// This may be null. Use <see cref="GetPreferredExchange(DefaultRulesCollection)"/> instead if you want to return a valid exchange
|
||||
/// </summary>
|
||||
public string PreferredExchange { get; set; }
|
||||
/// <summary>
|
||||
/// Use the preferred exchange of the store, or the recommended exchange from the default currency
|
||||
/// </summary>
|
||||
/// <param name="defaultRules"></param>
|
||||
/// <returns></returns>
|
||||
public string GetPreferredExchange(DefaultRulesCollection defaultRules)
|
||||
{
|
||||
return string.IsNullOrEmpty(PreferredExchange) ? defaultRules.GetRecommendedExchange(DefaultCurrency) : PreferredExchange;
|
||||
}
|
||||
|
||||
public List<PaymentMethodCriteria> PaymentMethodCriteria { get; set; }
|
||||
public string HtmlTitle { get; set; }
|
||||
@@ -135,18 +147,18 @@ namespace BTCPayServer.Data
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public double PaymentTolerance { get; set; }
|
||||
|
||||
public BTCPayServer.Rating.RateRules GetRateRules(IEnumerable<DefaultRates> defaultRates)
|
||||
public BTCPayServer.Rating.RateRules GetRateRules(DefaultRulesCollection defaultRules)
|
||||
{
|
||||
return GetRateRules(defaultRates, out _);
|
||||
return GetRateRules(defaultRules, out _);
|
||||
}
|
||||
public BTCPayServer.Rating.RateRules GetRateRules(IEnumerable<DefaultRates> defaultRates, out bool preferredSource)
|
||||
public BTCPayServer.Rating.RateRules GetRateRules(DefaultRulesCollection defaultRules, out bool preferredSource)
|
||||
{
|
||||
if (!RateScripting ||
|
||||
string.IsNullOrEmpty(RateScript) ||
|
||||
!BTCPayServer.Rating.RateRules.TryParse(RateScript, out var rules))
|
||||
{
|
||||
preferredSource = true;
|
||||
return GetDefaultRateRules(defaultRates);
|
||||
return GetDefaultRateRules(defaultRules);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -156,34 +168,13 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
public RateRules GetDefaultRateRules(IEnumerable<DefaultRates> defaultRates)
|
||||
public RateRules GetDefaultRateRules(DefaultRulesCollection defaultRules)
|
||||
{
|
||||
var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? GetRecommendedExchange() : PreferredExchange;
|
||||
var preferredExchangeRule = RateRules.Parse($"X_X = {preferredExchange}(X_X);");
|
||||
var rules = RateRules.Combine(defaultRates.Select(r => r.Rules).Concat([preferredExchangeRule]).ToArray());
|
||||
var rules = defaultRules.WithPreferredExchange(PreferredExchange);
|
||||
rules.Spread = Spread;
|
||||
return rules;
|
||||
}
|
||||
|
||||
public static JObject RecommendedExchanges = new()
|
||||
{
|
||||
{ "EUR", "kraken" },
|
||||
{ "USD", "kraken" },
|
||||
{ "GBP", "kraken" },
|
||||
{ "CHF", "kraken" },
|
||||
{ "GTQ", "bitpay" },
|
||||
{ "COP", "yadio" },
|
||||
{ "ARS", "yadio" },
|
||||
{ "JPY", "bitbank" },
|
||||
{ "TRY", "btcturk" },
|
||||
{ "UGX", "yadio"},
|
||||
{ "RSD", "bitpay"},
|
||||
{ "NGN", "bitnob"}
|
||||
};
|
||||
|
||||
public string GetRecommendedExchange() =>
|
||||
RecommendedExchanges.Property(DefaultCurrency)?.Value.ToString() ?? "coingecko";
|
||||
|
||||
[Obsolete("Use GetExcludedPaymentMethods instead")]
|
||||
public string[] ExcludedPaymentMethods { get; set; }
|
||||
|
||||
|
||||
@@ -53,8 +53,6 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(storeData);
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(storeData.StoreBlob);
|
||||
if (result.PreferredExchange == null)
|
||||
result.PreferredExchange = result.GetRecommendedExchange();
|
||||
if (result.PaymentMethodCriteria is null)
|
||||
result.PaymentMethodCriteria = new List<PaymentMethodCriteria>();
|
||||
result.PaymentMethodCriteria.RemoveAll(criteria => criteria?.PaymentMethod is null);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Rating;
|
||||
|
||||
namespace BTCPayServer;
|
||||
public record DefaultRates(RateRules Rules)
|
||||
{
|
||||
public DefaultRates(string[] Rules) : this(RateRules.Combine(Rules.Select(r => RateRules.Parse(r)).ToArray()))
|
||||
{
|
||||
}
|
||||
}
|
||||
72
BTCPayServer/DefaultRules.cs
Normal file
72
BTCPayServer/DefaultRules.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer;
|
||||
public record DefaultRules(RateRules Rules)
|
||||
{
|
||||
public record Recommendation : DefaultRules
|
||||
{
|
||||
public Recommendation(string currency, string exchange) : base($"X_{currency.ToUpperInvariant()} = {exchange.ToLowerInvariant()}(X_{currency.ToUpperInvariant()});")
|
||||
{
|
||||
Currency = currency.ToUpperInvariant();
|
||||
Exchange = exchange.ToLowerInvariant();
|
||||
}
|
||||
public string Currency { get; }
|
||||
public string Exchange { get; }
|
||||
}
|
||||
public const int HardcodedRecommendedExchangeOrder = 10;
|
||||
public DefaultRules(string Rules) : this(RateRules.Parse(Rules))
|
||||
{
|
||||
}
|
||||
public DefaultRules(string[] Rules) : this(RateRules.Combine(Rules.Select(r => RateRules.Parse(r)).ToArray()))
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Rules are applied in order, the lower the order, the higher the priority. Default is 0.
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
}
|
||||
|
||||
public class DefaultRulesCollection
|
||||
{
|
||||
public DefaultRulesCollection(IEnumerable<DefaultRules> defaultRules)
|
||||
{
|
||||
defaultRules = defaultRules.OrderBy(o => o.Order).ToList();
|
||||
Consolidated = RateRules.Combine(defaultRules.Select(r => r.Rules));
|
||||
ConsolidatedWithoutRecommendation = RateRules.Combine(defaultRules.Where(r => r is not DefaultRules.Recommendation).Select(r => r.Rules));
|
||||
var rules = Consolidated.ToString();
|
||||
|
||||
foreach (var recommendation in defaultRules.OfType<DefaultRules.Recommendation>())
|
||||
{
|
||||
RecommendedExchanges.TryAdd(recommendation.Currency, recommendation.Exchange);
|
||||
}
|
||||
}
|
||||
|
||||
public RateRules Consolidated { get; private set; }
|
||||
public RateRules ConsolidatedWithoutRecommendation { get; private set; }
|
||||
|
||||
public RateRules WithPreferredExchange(string? preferredExchange)
|
||||
{
|
||||
if (string.IsNullOrEmpty(preferredExchange))
|
||||
{
|
||||
return Consolidated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var catchAll = RateRules.Parse($"X_X = {preferredExchange}(X_X);");
|
||||
return RateRules.Combine([catchAll, ConsolidatedWithoutRecommendation]);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> RecommendedExchanges { get; } = new Dictionary<string, string>();
|
||||
|
||||
public string GetRecommendedExchange(string currency) =>
|
||||
RecommendedExchanges.TryGetValue(currency, out var ex) ? ex : "coingecko";
|
||||
|
||||
public override string ToString() => Consolidated.ToString();
|
||||
}
|
||||
@@ -279,7 +279,7 @@ namespace BTCPayServer.HostedServices
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
PayoutMethodHandlerDictionary handlers,
|
||||
IEnumerable<DefaultRates> defaultRates,
|
||||
DefaultRulesCollection defaultRules,
|
||||
NotificationSender notificationSender,
|
||||
RateFetcher rateFetcher,
|
||||
ILogger<PullPaymentHostedService> logger,
|
||||
@@ -292,7 +292,7 @@ namespace BTCPayServer.HostedServices
|
||||
_eventAggregator = eventAggregator;
|
||||
_networkProvider = networkProvider;
|
||||
_handlers = handlers;
|
||||
_defaultRates = defaultRates;
|
||||
_defaultRules = defaultRules;
|
||||
_notificationSender = notificationSender;
|
||||
_rateFetcher = rateFetcher;
|
||||
_logger = logger;
|
||||
@@ -306,7 +306,7 @@ namespace BTCPayServer.HostedServices
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
||||
private readonly DefaultRulesCollection _defaultRules;
|
||||
private readonly NotificationSender _notificationSender;
|
||||
private readonly RateFetcher _rateFetcher;
|
||||
private readonly ILogger<PullPaymentHostedService> _logger;
|
||||
@@ -392,7 +392,7 @@ namespace BTCPayServer.HostedServices
|
||||
if (explicitRateRule is null)
|
||||
{
|
||||
var storeBlob = payout.StoreData.GetStoreBlob();
|
||||
var rules = storeBlob.GetRateRules(_defaultRates);
|
||||
var rules = storeBlob.GetRateRules(_defaultRules);
|
||||
rules.Spread = 0.0m;
|
||||
rule = rules.GetRuleFor(currencyPair);
|
||||
}
|
||||
|
||||
@@ -403,6 +403,8 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
services.AddSingleton<NotificationManager>();
|
||||
services.AddScoped<NotificationSender>();
|
||||
|
||||
RegisterExchangeRecommendations(services);
|
||||
services.AddSingleton<DefaultRulesCollection>();
|
||||
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||
services.AddSingleton<IHostedService, InvoiceEventSaverService>();
|
||||
services.AddSingleton<IHostedService, BitpayIPNSender>();
|
||||
@@ -504,6 +506,30 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
return services;
|
||||
}
|
||||
|
||||
public static void RegisterExchangeRecommendations(IServiceCollection services)
|
||||
{
|
||||
foreach (var rule in new Dictionary<string, string>()
|
||||
{
|
||||
{ "EUR", "kraken" },
|
||||
{ "USD", "kraken" },
|
||||
{ "GBP", "kraken" },
|
||||
{ "CHF", "kraken" },
|
||||
{ "GTQ", "bitpay" },
|
||||
{ "COP", "yadio" },
|
||||
{ "ARS", "yadio" },
|
||||
{ "JPY", "bitbank" },
|
||||
{ "TRY", "btcturk" },
|
||||
{ "UGX", "yadio"},
|
||||
{ "RSD", "bitpay"},
|
||||
{ "NGN", "bitnob"}
|
||||
})
|
||||
{
|
||||
var r = new DefaultRules.Recommendation(rule.Key, rule.Value);
|
||||
r.Order = DefaultRules.HardcodedRecommendedExchangeOrder;
|
||||
services.AddSingleton<DefaultRules>(r);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddOnchainWalletParsers(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<WalletFileParsers>();
|
||||
@@ -563,13 +589,13 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
||||
}
|
||||
public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetworkBase network)
|
||||
{
|
||||
services.AddSingleton(new DefaultRates(network.DefaultRateRules));
|
||||
services.AddSingleton(new DefaultRules(network.DefaultRateRules));
|
||||
services.AddSingleton<BTCPayNetworkBase>(network);
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetwork network)
|
||||
{
|
||||
services.AddSingleton(new DefaultRates(network.DefaultRateRules));
|
||||
services.AddSingleton(new DefaultRules(network.DefaultRateRules));
|
||||
// BTC
|
||||
{
|
||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||
|
||||
@@ -17,15 +17,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public bool Error { get; set; }
|
||||
}
|
||||
|
||||
public void SetExchangeRates(IEnumerable<RateSourceInfo> supportedList, string preferredExchange)
|
||||
{
|
||||
supportedList = supportedList.ToArray();
|
||||
var chosen = supportedList.FirstOrDefault(f => f.Id == preferredExchange) ?? supportedList.FirstOrDefault();
|
||||
Exchanges = new SelectList(supportedList, nameof(chosen.Id), nameof(chosen.DisplayName), chosen);
|
||||
PreferredExchange = chosen?.Id;
|
||||
RateSource = chosen?.Url;
|
||||
}
|
||||
|
||||
public List<TestResultViewModel> TestRateRules { get; set; }
|
||||
|
||||
public SelectList Exchanges { get; set; }
|
||||
@@ -47,6 +38,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
[Display(Name = "Preferred Price Source")]
|
||||
public string PreferredExchange { get; set; }
|
||||
public string PreferredResolvedExchange { get; set; }
|
||||
|
||||
public string RateSource { get; set; }
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
<h4 class="mt-5">Customization</h4>
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="DefaultCurrency" class="form-label"></label>
|
||||
<input asp-for="DefaultCurrency" placeholder="Default Store Currency" class="form-control" currency-selection />
|
||||
<input asp-for="DefaultCurrency" placeholder="@StoreBlob.StandardDefaultCurrency" class="form-control" currency-selection />
|
||||
</div>
|
||||
<div class="form-group mb-5">
|
||||
<label asp-for="RootAppId" class="form-label"></label>
|
||||
|
||||
@@ -138,7 +138,7 @@ X_X = kraken(X_X);</code></pre>
|
||||
<select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-select"></select>
|
||||
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
|
||||
<div id="PreferredExchangeHelpBlock" class="form-text">
|
||||
Current Rates source is <a href="@Model.RateSource" target="_blank" rel="noreferrer noopener">@Model.PreferredExchange</a>.
|
||||
Current Rates source is <a href="@Model.RateSource" target="_blank" rel="noreferrer noopener">@Model.PreferredResolvedExchange</a>.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
||||
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
||||
@inject DefaultRulesCollection DefaultRules
|
||||
@{
|
||||
Layout = Model.IsFirstStore ? "_LayoutWizard" : "_Layout";
|
||||
ViewData.SetActivePage(StoreNavPages.Create, Model.IsFirstStore ? "Create your first store" : "Create a new store");
|
||||
@@ -7,12 +8,12 @@
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<script>
|
||||
const exchanges = @Safe.Json(StoreBlob.RecommendedExchanges);
|
||||
const exchanges = @Safe.Json(DefaultRules.RecommendedExchanges);
|
||||
const recommended = document.querySelector("#PreferredExchange option[value='']")
|
||||
const updateRecommended = currency => {
|
||||
const source = exchanges[currency] || 'coingecko'
|
||||
const name = source.charAt(0).toUpperCase() + source.slice(1)
|
||||
recommended.innerText = `${name} (Recommended)`
|
||||
recommended.innerText = `Recommendation (${name})`
|
||||
}
|
||||
updateRecommended(@Safe.Json(Model.DefaultCurrency))
|
||||
delegate('change', '#DefaultCurrency', e => updateRecommended(e.target.value))
|
||||
|
||||
Reference in New Issue
Block a user