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();
|
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()));
|
var str = string.Join(Environment.NewLine, rateRules.Select(r => r.ToString()));
|
||||||
return Parse(str);
|
return Parse(str);
|
||||||
|
|||||||
@@ -4284,7 +4284,7 @@ namespace BTCPayServer.Tests
|
|||||||
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
config = await clientBasic.GetStoreRateConfiguration(user.StoreId);
|
||||||
Assert.Equal("X_X = coingecko(X_X);", config.EffectiveScript);
|
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;" }));
|
clientBasic.UpdateStoreRateConfiguration(user.StoreId, new StoreRateConfiguration() { IsCustomScript = false, EffectiveScript = "BTC_XYZ = 1;" }));
|
||||||
|
|
||||||
await AssertValidationError(new[] { "EffectiveScript" }, () =>
|
await AssertValidationError(new[] { "EffectiveScript" }, () =>
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ retry:
|
|||||||
TestLogs.LogInformation($"Created store {name}");
|
TestLogs.LogInformation($"Created store {name}");
|
||||||
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
||||||
var rateSource = new SelectElement(Driver.FindElement(By.Id("PreferredExchange")));
|
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");
|
rateSource.SelectByText("CoinGecko");
|
||||||
Driver.WaitForElement(By.Id("Create")).Click();
|
Driver.WaitForElement(By.Id("Create")).Click();
|
||||||
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
|
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Hosting;
|
||||||
using BTCPayServer.Models.StoreViewModels;
|
using BTCPayServer.Models.StoreViewModels;
|
||||||
using BTCPayServer.Models.WalletViewModels;
|
using BTCPayServer.Models.WalletViewModels;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
@@ -357,12 +358,13 @@ retry:
|
|||||||
var factory = FastTests.CreateBTCPayRateFactory();
|
var factory = FastTests.CreateBTCPayRateFactory();
|
||||||
var fetcher = new RateFetcher(factory);
|
var fetcher = new RateFetcher(factory);
|
||||||
var provider = CreateDefaultRates(ChainName.Mainnet);
|
var provider = CreateDefaultRates(ChainName.Mainnet);
|
||||||
|
var defaultRules = new DefaultRulesCollection(provider.Select(p => p.DefaultRates));
|
||||||
var b = new StoreBlob();
|
var b = new StoreBlob();
|
||||||
string[] temporarilyBroken = Array.Empty<string>();
|
string[] temporarilyBroken = Array.Empty<string>();
|
||||||
foreach (var k in StoreBlob.RecommendedExchanges)
|
foreach (var k in defaultRules.RecommendedExchanges)
|
||||||
{
|
{
|
||||||
b.DefaultCurrency = k.Key;
|
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 pairs = new[] { CurrencyPair.Parse($"BTC_{k.Key}") }.ToHashSet();
|
||||||
var result = fetcher.FetchRates(pairs, rules, null, default);
|
var result = fetcher.FetchRates(pairs, rules, null, default);
|
||||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
||||||
@@ -389,11 +391,13 @@ retry:
|
|||||||
public async Task CanGetRateCryptoCurrenciesByDefault()
|
public async Task CanGetRateCryptoCurrenciesByDefault()
|
||||||
{
|
{
|
||||||
using var cts = new CancellationTokenSource(60_000);
|
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 factory = FastTests.CreateBTCPayRateFactory();
|
||||||
var fetcher = new RateFetcher(factory);
|
var fetcher = new RateFetcher(factory);
|
||||||
var pairs =
|
var pairs =
|
||||||
provider
|
provider
|
||||||
|
.Where(c => c.CryptoCode is not null)
|
||||||
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
|
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
|
||||||
.ToHashSet();
|
.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);
|
var result = fetcher.FetchRates(pairs, rules, null, cts.Token);
|
||||||
foreach ((CurrencyPair key, Task<RateResult> value) in result)
|
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);
|
var prov = CreateNetworkProvider(chainName);
|
||||||
foreach (var network in prov.GetAll())
|
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;
|
return results;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Components.WalletNav
|
|||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly UIWalletsController _walletsController;
|
private readonly UIWalletsController _walletsController;
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly RateFetcher _rateFetcher;
|
private readonly RateFetcher _rateFetcher;
|
||||||
|
|
||||||
public WalletNav(
|
public WalletNav(
|
||||||
@@ -39,14 +39,14 @@ namespace BTCPayServer.Components.WalletNav
|
|||||||
PaymentMethodHandlerDictionary handlers,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
UIWalletsController walletsController,
|
UIWalletsController walletsController,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
RateFetcher rateFetcher)
|
RateFetcher rateFetcher)
|
||||||
{
|
{
|
||||||
_walletProvider = walletProvider;
|
_walletProvider = walletProvider;
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_walletsController = walletsController;
|
_walletsController = walletsController;
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_rateFetcher = rateFetcher;
|
_rateFetcher = rateFetcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ namespace BTCPayServer.Components.WalletNav
|
|||||||
|
|
||||||
if (defaultCurrency != network.CryptoCode)
|
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;
|
var bid = rule is null ? null : (await _rateFetcher.FetchRate(rule, new StoreIdRateContext(walletId.StoreId), HttpContext.RequestAborted)).BidAsk?.Bid;
|
||||||
if (bid is decimal b)
|
if (bid is decimal b)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
readonly RateFetcher _rateProviderFactory;
|
readonly RateFetcher _rateProviderFactory;
|
||||||
readonly CurrencyNameTable _currencyNameTable;
|
readonly CurrencyNameTable _currencyNameTable;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
readonly StoreRepository _storeRepo;
|
readonly StoreRepository _storeRepo;
|
||||||
private readonly InvoiceRepository _invoiceRepository;
|
private readonly InvoiceRepository _invoiceRepository;
|
||||||
@@ -43,14 +43,14 @@ namespace BTCPayServer.Controllers
|
|||||||
StoreRepository storeRepo,
|
StoreRepository storeRepo,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
CurrencyNameTable currencyNameTable,
|
CurrencyNameTable currencyNameTable,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
PaymentMethodHandlerDictionary handlers)
|
PaymentMethodHandlerDictionary handlers)
|
||||||
{
|
{
|
||||||
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
_rateProviderFactory = rateProviderFactory ?? throw new ArgumentNullException(nameof(rateProviderFactory));
|
||||||
_storeRepo = storeRepo;
|
_storeRepo = storeRepo;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
_currencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_handlers = handlers;
|
_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>();
|
var pairs = new HashSet<CurrencyPair>();
|
||||||
foreach (var currency in currencyPairs.Split(','))
|
foreach (var currency in currencyPairs.Split(','))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
private readonly Dictionary<PaymentMethodId, IPaymentLinkExtension> _paymentLinkExtensions;
|
||||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
|
|
||||||
public LanguageService LanguageService { get; }
|
public LanguageService LanguageService { get; }
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
Dictionary<PaymentMethodId, IPaymentLinkExtension> paymentLinkExtensions,
|
||||||
PayoutMethodHandlerDictionary payoutHandlers,
|
PayoutMethodHandlerDictionary payoutHandlers,
|
||||||
PaymentMethodHandlerDictionary handlers,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
IEnumerable<DefaultRates> defaultRates)
|
DefaultRulesCollection defaultRules)
|
||||||
{
|
{
|
||||||
_invoiceController = invoiceController;
|
_invoiceController = invoiceController;
|
||||||
_invoiceRepository = invoiceRepository;
|
_invoiceRepository = invoiceRepository;
|
||||||
@@ -76,7 +76,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
_paymentLinkExtensions = paymentLinkExtensions;
|
_paymentLinkExtensions = paymentLinkExtensions;
|
||||||
_payoutHandlers = payoutHandlers;
|
_payoutHandlers = payoutHandlers;
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
LanguageService = languageService;
|
LanguageService = languageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
|
var paidCurrency = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
|
||||||
var rateResult = await _rateProvider.FetchRate(
|
var rateResult = await _rateProvider.FetchRate(
|
||||||
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
|
new CurrencyPair(paymentPrompt.Currency, invoice.Currency),
|
||||||
store.GetStoreBlob().GetRateRules(_defaultRates), new StoreIdRateContext(storeId),
|
store.GetStoreBlob().GetRateRules(_defaultRules), new StoreIdRateContext(storeId),
|
||||||
|
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
public class GreenfieldStoreRateConfigurationController : ControllerBase
|
public class GreenfieldStoreRateConfigurationController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly RateFetcher _rateProviderFactory;
|
private readonly RateFetcher _rateProviderFactory;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
|
|
||||||
public GreenfieldStoreRateConfigurationController(
|
public GreenfieldStoreRateConfigurationController(
|
||||||
RateFetcher rateProviderFactory,
|
RateFetcher rateProviderFactory,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
StoreRepository storeRepository)
|
StoreRepository storeRepository)
|
||||||
{
|
{
|
||||||
_rateProviderFactory = rateProviderFactory;
|
_rateProviderFactory = rateProviderFactory;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,10 +49,10 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
|
|
||||||
return Ok(new StoreRateConfiguration()
|
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,
|
Spread = blob.Spread * 100.0m,
|
||||||
IsCustomScript = blob.RateScripting,
|
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);
|
return this.CreateValidationError(ModelState);
|
||||||
PopulateBlob(configuration, blob);
|
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);
|
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))
|
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))
|
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),
|
ModelState.AddModelError(nameof(configuration.EffectiveScript),
|
||||||
$"You can't set the effectiveScript if you aren't using custom scripts");
|
$"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
|
configuration.PreferredSource = _rateProviderFactory
|
||||||
.RateProviderFactory
|
.RateProviderFactory
|
||||||
.AvailableRateProviders
|
.AvailableRateProviders
|
||||||
@@ -202,6 +198,7 @@ $"The preferredSource is required if you aren't using custom scripts");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void PopulateBlob(StoreRateConfiguration configuration, StoreBlob storeBlob)
|
private static void PopulateBlob(StoreRateConfiguration configuration, StoreBlob storeBlob)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ namespace BTCPayServer.Controllers.GreenField
|
|||||||
public class GreenfieldStoreRatesController : ControllerBase
|
public class GreenfieldStoreRatesController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly RateFetcher _rateProviderFactory;
|
private readonly RateFetcher _rateProviderFactory;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
|
|
||||||
public GreenfieldStoreRatesController(
|
public GreenfieldStoreRatesController(
|
||||||
RateFetcher rateProviderFactory,
|
RateFetcher rateProviderFactory,
|
||||||
IEnumerable<DefaultRates> defaultRates)
|
DefaultRulesCollection defaultRules)
|
||||||
{
|
{
|
||||||
_rateProviderFactory = rateProviderFactory;
|
_rateProviderFactory = rateProviderFactory;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[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);
|
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);
|
var paidCurrency = Math.Round(cryptoPaid * paymentMethod.Rate, cdCurrency.Divisibility);
|
||||||
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
|
model.CryptoAmountThen = cryptoPaid.RoundToSignificant(paymentMethod.Divisibility);
|
||||||
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodCurrency);
|
model.RateThenText = _displayFormatter.Currency(model.CryptoAmountThen, paymentMethodCurrency);
|
||||||
rules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
rules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||||
rateResult = await _RateProvider.FetchRate(
|
rateResult = await _RateProvider.FetchRate(
|
||||||
new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, new StoreIdRateContext(store.Id),
|
new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, new StoreIdRateContext(store.Id),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
@@ -491,7 +491,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return View("_RefundModal", model);
|
return View("_RefundModal", model);
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
rules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||||
rateResult = await _RateProvider.FetchRate(
|
rateResult = await _RateProvider.FetchRate(
|
||||||
new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules, new StoreIdRateContext(store.Id),
|
new CurrencyPair(paymentMethodCurrency, model.CustomCurrency), rules, new StoreIdRateContext(store.Id),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace BTCPayServer.Controllers
|
|||||||
readonly BTCPayNetworkProvider _NetworkProvider;
|
readonly BTCPayNetworkProvider _NetworkProvider;
|
||||||
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
private readonly PayoutMethodHandlerDictionary _payoutHandlers;
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||||
private readonly PullPaymentHostedService _paymentHostedService;
|
private readonly PullPaymentHostedService _paymentHostedService;
|
||||||
private readonly LanguageService _languageService;
|
private readonly LanguageService _languageService;
|
||||||
@@ -94,7 +94,7 @@ namespace BTCPayServer.Controllers
|
|||||||
AppService appService,
|
AppService appService,
|
||||||
IFileService fileService,
|
IFileService fileService,
|
||||||
UriResolver uriResolver,
|
UriResolver uriResolver,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
TransactionLinkProviders transactionLinkProviders,
|
TransactionLinkProviders transactionLinkProviders,
|
||||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||||
@@ -125,7 +125,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_viewProvider = viewProvider;
|
_viewProvider = viewProvider;
|
||||||
_fileService = fileService;
|
_fileService = fileService;
|
||||||
_uriResolver = uriResolver;
|
_uriResolver = uriResolver;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +297,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private async Task FetchRates(InvoiceCreationContext context, CancellationToken cancellationToken)
|
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);
|
await context.FetchingRates(_RateProvider, rateRules, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using BTCPayServer.Rating;
|
|||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static BTCPayServer.Lightning.Eclair.Models.ChannelResponse;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers;
|
namespace BTCPayServer.Controllers;
|
||||||
|
|
||||||
@@ -22,17 +23,9 @@ public partial class UIStoresController
|
|||||||
[HttpGet("{storeId}/rates")]
|
[HttpGet("{storeId}/rates")]
|
||||||
public IActionResult Rates()
|
public IActionResult Rates()
|
||||||
{
|
{
|
||||||
var exchanges = GetSupportedExchanges().ToList();
|
|
||||||
var storeBlob = CurrentStore.GetStoreBlob();
|
var storeBlob = CurrentStore.GetStoreBlob();
|
||||||
var vm = new RatesViewModel();
|
var vm = new RatesViewModel();
|
||||||
vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? storeBlob.GetRecommendedExchange());
|
FillFromStore(vm, storeBlob);
|
||||||
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;
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +41,6 @@ public partial class UIStoresController
|
|||||||
{
|
{
|
||||||
return RedirectToAction(nameof(ShowRateRules), new { scripting = false, storeId = model.StoreId });
|
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;
|
model.StoreId = storeId ?? model.StoreId;
|
||||||
CurrencyPair[]? currencyPairs = null;
|
CurrencyPair[]? currencyPairs = null;
|
||||||
try
|
try
|
||||||
@@ -70,22 +60,10 @@ public partial class UIStoresController
|
|||||||
}
|
}
|
||||||
if (model.PreferredExchange != null)
|
if (model.PreferredExchange != null)
|
||||||
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
|
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
|
||||||
|
if (string.IsNullOrEmpty(model.PreferredExchange))
|
||||||
|
model.PreferredExchange = null;
|
||||||
|
|
||||||
var blob = CurrentStore.GetStoreBlob();
|
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;
|
RateRules? rules;
|
||||||
if (model.ShowScripting)
|
if (model.ShowScripting)
|
||||||
{
|
{
|
||||||
@@ -94,17 +72,20 @@ public partial class UIStoresController
|
|||||||
errors ??= [];
|
errors ??= [];
|
||||||
var errorString = string.Join(", ", errors.ToArray());
|
var errorString = string.Join(", ", errors.ToArray());
|
||||||
ModelState.AddModelError(nameof(model.Script), $"Parsing error ({errorString})");
|
ModelState.AddModelError(nameof(model.Script), $"Parsing error ({errorString})");
|
||||||
|
FillFromStore(model, blob);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
blob.RateScript = rules.ToString();
|
blob.RateScript = rules.ToString();
|
||||||
ModelState.Remove(nameof(model.Script));
|
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 (command == "Test")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(model.ScriptTest))
|
if (string.IsNullOrWhiteSpace(model.ScriptTest))
|
||||||
@@ -142,6 +123,12 @@ public partial class UIStoresController
|
|||||||
return View(model);
|
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
|
// command == Save
|
||||||
if (CurrentStore.SetStoreBlob(blob))
|
if (CurrentStore.SetStoreBlob(blob))
|
||||||
{
|
{
|
||||||
@@ -175,16 +162,29 @@ public partial class UIStoresController
|
|||||||
{
|
{
|
||||||
var blob = CurrentStore.GetStoreBlob();
|
var blob = CurrentStore.GetStoreBlob();
|
||||||
blob.RateScripting = scripting;
|
blob.RateScripting = scripting;
|
||||||
blob.RateScript = blob.GetDefaultRateRules(_defaultRates).ToString();
|
blob.RateScript = blob.GetDefaultRateRules(_defaultRules).ToString();
|
||||||
CurrentStore.SetStoreBlob(blob);
|
CurrentStore.SetStoreBlob(blob);
|
||||||
await _storeRepo.UpdateStore(CurrentStore);
|
await _storeRepo.UpdateStore(CurrentStore);
|
||||||
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting " + (scripting ? "activated" : "deactivated");
|
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting " + (scripting ? "activated" : "deactivated");
|
||||||
return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id });
|
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);
|
.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<LightningNetworkOptions> lightningNetworkOptions,
|
||||||
IOptions<ExternalServicesOptions> externalServiceOptions,
|
IOptions<ExternalServicesOptions> externalServiceOptions,
|
||||||
IHtmlHelper html,
|
IHtmlHelper html,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
EmailSenderFactory emailSenderFactory,
|
EmailSenderFactory emailSenderFactory,
|
||||||
WalletFileParsers onChainWalletParsers,
|
WalletFileParsers onChainWalletParsers,
|
||||||
UriResolver uriResolver,
|
UriResolver uriResolver,
|
||||||
@@ -85,7 +85,7 @@ public partial class UIStoresController : Controller
|
|||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_html = html;
|
_html = html;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_dataProtector = dataProtector.CreateProtector("ConfigProtector");
|
_dataProtector = dataProtector.CreateProtector("ConfigProtector");
|
||||||
_webhookNotificationManager = webhookNotificationManager;
|
_webhookNotificationManager = webhookNotificationManager;
|
||||||
_lightningNetworkOptions = lightningNetworkOptions.Value;
|
_lightningNetworkOptions = lightningNetworkOptions.Value;
|
||||||
@@ -104,7 +104,7 @@ public partial class UIStoresController : Controller
|
|||||||
private readonly ExplorerClientProvider _explorerProvider;
|
private readonly ExplorerClientProvider _explorerProvider;
|
||||||
private readonly LanguageService _langService;
|
private readonly LanguageService _langService;
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly PoliciesSettings _policiesSettings;
|
private readonly PoliciesSettings _policiesSettings;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
|
|||||||
@@ -23,17 +23,20 @@ namespace BTCPayServer.Controllers
|
|||||||
private readonly StoreRepository _repo;
|
private readonly StoreRepository _repo;
|
||||||
private readonly SettingsRepository _settingsRepository;
|
private readonly SettingsRepository _settingsRepository;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly RateFetcher _rateFactory;
|
private readonly RateFetcher _rateFactory;
|
||||||
public string CreatedStoreId { get; set; }
|
public string CreatedStoreId { get; set; }
|
||||||
|
|
||||||
public UIUserStoresController(
|
public UIUserStoresController(
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
|
DefaultRulesCollection defaultRules,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
RateFetcher rateFactory,
|
RateFetcher rateFactory,
|
||||||
SettingsRepository settingsRepository)
|
SettingsRepository settingsRepository)
|
||||||
{
|
{
|
||||||
_repo = storeRepository;
|
_repo = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_defaultRules = defaultRules;
|
||||||
_rateFactory = rateFactory;
|
_rateFactory = rateFactory;
|
||||||
_settingsRepository = settingsRepository;
|
_settingsRepository = settingsRepository;
|
||||||
}
|
}
|
||||||
@@ -81,7 +84,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
var stores = await _repo.GetStoresByUserId(GetUserId());
|
var stores = await _repo.GetStoresByUserId(GetUserId());
|
||||||
vm.IsFirstStore = !stores.Any();
|
vm.IsFirstStore = !stores.Any();
|
||||||
vm.Exchanges = GetExchangesSelectList(vm.PreferredExchange);
|
vm.Exchanges = GetExchangesSelectList(null);
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +127,19 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private string GetUserId() => _userManager.GetUserId(User);
|
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
|
.AvailableRateProviders
|
||||||
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
exchanges.Insert(0, new (null, "Recommended", ""));
|
var exchange = exchanges.First(e => e.Id == defaultExchange);
|
||||||
var chosen = exchanges.FirstOrDefault(f => f.Id == selected) ?? exchanges.First();
|
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);
|
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 PayjoinClient _payjoinClient;
|
||||||
private readonly LabelService _labelService;
|
private readonly LabelService _labelService;
|
||||||
private readonly PaymentMethodHandlerDictionary _handlers;
|
private readonly PaymentMethodHandlerDictionary _handlers;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
|
||||||
private readonly TransactionLinkProviders _transactionLinkProviders;
|
private readonly TransactionLinkProviders _transactionLinkProviders;
|
||||||
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
private readonly PullPaymentHostedService _pullPaymentHostedService;
|
||||||
@@ -100,14 +100,14 @@ namespace BTCPayServer.Controllers
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
PullPaymentHostedService pullPaymentHostedService,
|
PullPaymentHostedService pullPaymentHostedService,
|
||||||
LabelService labelService,
|
LabelService labelService,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
PaymentMethodHandlerDictionary handlers,
|
PaymentMethodHandlerDictionary handlers,
|
||||||
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
|
||||||
TransactionLinkProviders transactionLinkProviders)
|
TransactionLinkProviders transactionLinkProviders)
|
||||||
{
|
{
|
||||||
_currencyTable = currencyTable;
|
_currencyTable = currencyTable;
|
||||||
_labelService = labelService;
|
_labelService = labelService;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_paymentModelExtensions = paymentModelExtensions;
|
_paymentModelExtensions = paymentModelExtensions;
|
||||||
_transactionLinkProviders = transactionLinkProviders;
|
_transactionLinkProviders = transactionLinkProviders;
|
||||||
@@ -459,7 +459,7 @@ namespace BTCPayServer.Controllers
|
|||||||
if (network == null || network.ReadonlyWallet)
|
if (network == null || network.ReadonlyWallet)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var storeData = store.GetStoreBlob();
|
var storeData = store.GetStoreBlob();
|
||||||
var rateRules = store.GetStoreBlob().GetRateRules(_defaultRates);
|
var rateRules = store.GetStoreBlob().GetRateRules(_defaultRules);
|
||||||
rateRules.Spread = 0.0m;
|
rateRules.Spread = 0.0m;
|
||||||
var currencyPair = new Rating.CurrencyPair(walletId.CryptoCode, storeData.DefaultCurrency);
|
var currencyPair = new Rating.CurrencyPair(walletId.CryptoCode, storeData.DefaultCurrency);
|
||||||
double.TryParse(defaultAmount, out var amount);
|
double.TryParse(defaultAmount, out var amount);
|
||||||
|
|||||||
@@ -102,7 +102,19 @@ namespace BTCPayServer.Data
|
|||||||
|
|
||||||
public decimal Spread { get; set; } = 0.0m;
|
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; }
|
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 List<PaymentMethodCriteria> PaymentMethodCriteria { get; set; }
|
||||||
public string HtmlTitle { get; set; }
|
public string HtmlTitle { get; set; }
|
||||||
@@ -135,18 +147,18 @@ namespace BTCPayServer.Data
|
|||||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||||
public double PaymentTolerance { get; set; }
|
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 ||
|
if (!RateScripting ||
|
||||||
string.IsNullOrEmpty(RateScript) ||
|
string.IsNullOrEmpty(RateScript) ||
|
||||||
!BTCPayServer.Rating.RateRules.TryParse(RateScript, out var rules))
|
!BTCPayServer.Rating.RateRules.TryParse(RateScript, out var rules))
|
||||||
{
|
{
|
||||||
preferredSource = true;
|
preferredSource = true;
|
||||||
return GetDefaultRateRules(defaultRates);
|
return GetDefaultRateRules(defaultRules);
|
||||||
}
|
}
|
||||||
else
|
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 rules = defaultRules.WithPreferredExchange(PreferredExchange);
|
||||||
var preferredExchangeRule = RateRules.Parse($"X_X = {preferredExchange}(X_X);");
|
|
||||||
var rules = RateRules.Combine(defaultRates.Select(r => r.Rules).Concat([preferredExchangeRule]).ToArray());
|
|
||||||
rules.Spread = Spread;
|
rules.Spread = Spread;
|
||||||
return rules;
|
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")]
|
[Obsolete("Use GetExcludedPaymentMethods instead")]
|
||||||
public string[] ExcludedPaymentMethods { get; set; }
|
public string[] ExcludedPaymentMethods { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ namespace BTCPayServer.Data
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(storeData);
|
ArgumentNullException.ThrowIfNull(storeData);
|
||||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(storeData.StoreBlob);
|
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)
|
if (result.PaymentMethodCriteria is null)
|
||||||
result.PaymentMethodCriteria = new List<PaymentMethodCriteria>();
|
result.PaymentMethodCriteria = new List<PaymentMethodCriteria>();
|
||||||
result.PaymentMethodCriteria.RemoveAll(criteria => criteria?.PaymentMethod is null);
|
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,
|
EventAggregator eventAggregator,
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
PayoutMethodHandlerDictionary handlers,
|
PayoutMethodHandlerDictionary handlers,
|
||||||
IEnumerable<DefaultRates> defaultRates,
|
DefaultRulesCollection defaultRules,
|
||||||
NotificationSender notificationSender,
|
NotificationSender notificationSender,
|
||||||
RateFetcher rateFetcher,
|
RateFetcher rateFetcher,
|
||||||
ILogger<PullPaymentHostedService> logger,
|
ILogger<PullPaymentHostedService> logger,
|
||||||
@@ -292,7 +292,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_networkProvider = networkProvider;
|
_networkProvider = networkProvider;
|
||||||
_handlers = handlers;
|
_handlers = handlers;
|
||||||
_defaultRates = defaultRates;
|
_defaultRules = defaultRules;
|
||||||
_notificationSender = notificationSender;
|
_notificationSender = notificationSender;
|
||||||
_rateFetcher = rateFetcher;
|
_rateFetcher = rateFetcher;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -306,7 +306,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
private readonly EventAggregator _eventAggregator;
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly BTCPayNetworkProvider _networkProvider;
|
private readonly BTCPayNetworkProvider _networkProvider;
|
||||||
private readonly PayoutMethodHandlerDictionary _handlers;
|
private readonly PayoutMethodHandlerDictionary _handlers;
|
||||||
private readonly IEnumerable<DefaultRates> _defaultRates;
|
private readonly DefaultRulesCollection _defaultRules;
|
||||||
private readonly NotificationSender _notificationSender;
|
private readonly NotificationSender _notificationSender;
|
||||||
private readonly RateFetcher _rateFetcher;
|
private readonly RateFetcher _rateFetcher;
|
||||||
private readonly ILogger<PullPaymentHostedService> _logger;
|
private readonly ILogger<PullPaymentHostedService> _logger;
|
||||||
@@ -392,7 +392,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
if (explicitRateRule is null)
|
if (explicitRateRule is null)
|
||||||
{
|
{
|
||||||
var storeBlob = payout.StoreData.GetStoreBlob();
|
var storeBlob = payout.StoreData.GetStoreBlob();
|
||||||
var rules = storeBlob.GetRateRules(_defaultRates);
|
var rules = storeBlob.GetRateRules(_defaultRules);
|
||||||
rules.Spread = 0.0m;
|
rules.Spread = 0.0m;
|
||||||
rule = rules.GetRuleFor(currencyPair);
|
rule = rules.GetRuleFor(currencyPair);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,8 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||||||
services.AddSingleton<NotificationManager>();
|
services.AddSingleton<NotificationManager>();
|
||||||
services.AddScoped<NotificationSender>();
|
services.AddScoped<NotificationSender>();
|
||||||
|
|
||||||
|
RegisterExchangeRecommendations(services);
|
||||||
|
services.AddSingleton<DefaultRulesCollection>();
|
||||||
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||||
services.AddSingleton<IHostedService, InvoiceEventSaverService>();
|
services.AddSingleton<IHostedService, InvoiceEventSaverService>();
|
||||||
services.AddSingleton<IHostedService, BitpayIPNSender>();
|
services.AddSingleton<IHostedService, BitpayIPNSender>();
|
||||||
@@ -504,6 +506,30 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||||||
return services;
|
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)
|
public static void AddOnchainWalletParsers(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<WalletFileParsers>();
|
services.AddSingleton<WalletFileParsers>();
|
||||||
@@ -563,13 +589,13 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
|
|||||||
}
|
}
|
||||||
public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetworkBase network)
|
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);
|
services.AddSingleton<BTCPayNetworkBase>(network);
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetwork network)
|
public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetwork network)
|
||||||
{
|
{
|
||||||
services.AddSingleton(new DefaultRates(network.DefaultRateRules));
|
services.AddSingleton(new DefaultRules(network.DefaultRateRules));
|
||||||
// BTC
|
// BTC
|
||||||
{
|
{
|
||||||
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode);
|
||||||
|
|||||||
@@ -17,15 +17,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
public bool Error { get; set; }
|
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 List<TestResultViewModel> TestRateRules { get; set; }
|
||||||
|
|
||||||
public SelectList Exchanges { get; set; }
|
public SelectList Exchanges { get; set; }
|
||||||
@@ -47,6 +38,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||||||
|
|
||||||
[Display(Name = "Preferred Price Source")]
|
[Display(Name = "Preferred Price Source")]
|
||||||
public string PreferredExchange { get; set; }
|
public string PreferredExchange { get; set; }
|
||||||
|
public string PreferredResolvedExchange { get; set; }
|
||||||
|
|
||||||
public string RateSource { get; set; }
|
public string RateSource { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
<h4 class="mt-5">Customization</h4>
|
<h4 class="mt-5">Customization</h4>
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<label asp-for="DefaultCurrency" class="form-label"></label>
|
<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>
|
||||||
<div class="form-group mb-5">
|
<div class="form-group mb-5">
|
||||||
<label asp-for="RootAppId" class="form-label"></label>
|
<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>
|
<select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-select"></select>
|
||||||
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
|
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
|
||||||
<div id="PreferredExchangeHelpBlock" class="form-text">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
@model BTCPayServer.Models.StoreViewModels.CreateStoreViewModel
|
||||||
|
@inject DefaultRulesCollection DefaultRules
|
||||||
@{
|
@{
|
||||||
Layout = Model.IsFirstStore ? "_LayoutWizard" : "_Layout";
|
Layout = Model.IsFirstStore ? "_LayoutWizard" : "_Layout";
|
||||||
ViewData.SetActivePage(StoreNavPages.Create, Model.IsFirstStore ? "Create your first store" : "Create a new store");
|
ViewData.SetActivePage(StoreNavPages.Create, Model.IsFirstStore ? "Create your first store" : "Create a new store");
|
||||||
@@ -7,12 +8,12 @@
|
|||||||
@section PageFootContent {
|
@section PageFootContent {
|
||||||
<partial name="_ValidationScriptsPartial" />
|
<partial name="_ValidationScriptsPartial" />
|
||||||
<script>
|
<script>
|
||||||
const exchanges = @Safe.Json(StoreBlob.RecommendedExchanges);
|
const exchanges = @Safe.Json(DefaultRules.RecommendedExchanges);
|
||||||
const recommended = document.querySelector("#PreferredExchange option[value='']")
|
const recommended = document.querySelector("#PreferredExchange option[value='']")
|
||||||
const updateRecommended = currency => {
|
const updateRecommended = currency => {
|
||||||
const source = exchanges[currency] || 'coingecko'
|
const source = exchanges[currency] || 'coingecko'
|
||||||
const name = source.charAt(0).toUpperCase() + source.slice(1)
|
const name = source.charAt(0).toUpperCase() + source.slice(1)
|
||||||
recommended.innerText = `${name} (Recommended)`
|
recommended.innerText = `Recommendation (${name})`
|
||||||
}
|
}
|
||||||
updateRecommended(@Safe.Json(Model.DefaultCurrency))
|
updateRecommended(@Safe.Json(Model.DefaultCurrency))
|
||||||
delegate('change', '#DefaultCurrency', e => updateRecommended(e.target.value))
|
delegate('change', '#DefaultCurrency', e => updateRecommended(e.target.value))
|
||||||
|
|||||||
Reference in New Issue
Block a user