Merge pull request #3337 from dennisreimann/merge-general-payment

Store: Combine General and Payment settings
This commit is contained in:
Nicolas Dorier 2022-01-25 20:26:45 +09:00 committed by GitHub
commit fe9de98dd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 216 additions and 245 deletions

View file

@ -1,3 +1,5 @@
using System;
namespace BTCPayServer.Rating
{
public enum RateSource
@ -25,5 +27,13 @@ namespace BTCPayServer.Rating
Url = url;
Source = source;
}
public string DisplayName =>
Source switch
{
RateSource.Direct => Name,
RateSource.Coingecko => $"{Name} (via CoinGecko)",
_ => throw new NotSupportedException(Source.ToString())
};
}
}

View file

@ -386,7 +386,7 @@ namespace BTCPayServer.Tests
// BTC crash by 50%
s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m));
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
s.Driver.FindElement(By.Id("BOLT11Expiration")).Clear();
s.Driver.FindElement(By.Id("BOLT11Expiration")).SendKeys("5" + Keys.Enter);
s.GoToInvoice(invoice.Id);
@ -438,7 +438,7 @@ namespace BTCPayServer.Tests
s.GoToInvoiceCheckout(invoiceId);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome();
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
s.AddDerivationScheme("LTC");
s.AddLightningNode(LightningConnectionType.CLightning);
//there should be three now

View file

@ -204,7 +204,7 @@ namespace BTCPayServer.Tests
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);

View file

@ -156,7 +156,6 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.General.ToString()}")).Click();
var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.Payment.ToString()}")).Click();
if (keepId)
StoreId = storeId;
return (name, storeId);
@ -397,7 +396,6 @@ namespace BTCPayServer.Tests
public void GoToLightningSettings(string cryptoCode = "BTC")
{
GoToStore(StoreNavPages.Payment);
Driver.FindElement(By.Id($"StoreNav-Lightning{cryptoCode}")).Click();
// if Lightning is already set up we need to navigate to the settings
if (Driver.PageSource.Contains("id=\"SectionNav-LightningSettings\""))

View file

@ -404,7 +404,7 @@ namespace BTCPayServer.Tests
(string storeName, string storeId) = s.CreateNewStore();
var storeUrl = $"/stores/{storeId}";
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
Assert.Contains(storeName, s.Driver.PageSource);
// verify steps for wallet setup are displayed correctly
@ -793,7 +793,7 @@ namespace BTCPayServer.Tests
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
TestLogs.LogInformation("Let's see if we can generate an event");
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
s.AddDerivationScheme();
s.CreateInvoice();
var request = await server.GetNextRequest();
@ -929,7 +929,7 @@ namespace BTCPayServer.Tests
var result =
await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
Assert.True(result.IsWatchOnly);
s.GoToStore(storeId, StoreNavPages.Payment);
s.GoToStore(storeId);
var mnemonic = s.GenerateWallet(cryptoCode, "", true, true);
//lets import and save private keys
@ -1302,7 +1302,7 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true);
s.CreateNewStore();
s.GoToStore(StoreNavPages.Payment);
s.GoToStore();
s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
@ -1345,7 +1345,6 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true);
(_, string storeId) = s.CreateNewStore();
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
s.GoToStore(StoreNavPages.Payment);
s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToLightningSettings();
// LNURL is true by default
@ -1558,7 +1557,6 @@ namespace BTCPayServer.Tests
//ensure ln address is not available as Lightning is not enable
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
s.GoToStore(s.StoreId, StoreNavPages.Payment);
s.AddLightningNode(LightningConnectionType.LndREST, false);
s.Driver.FindElement(By.Id("StoreNav-LightningAddress")).Click();

View file

@ -133,13 +133,13 @@ namespace BTCPayServer.Tests
});
}
public async Task ModifyPayment(Action<PaymentViewModel> modify)
public async Task ModifyPayment(Action<GeneralSettingsViewModel> modify)
{
var storeController = GetController<UIStoresController>();
var response = storeController.Payment();
PaymentViewModel payment = (PaymentViewModel)((ViewResult)response).Model;
modify(payment);
await storeController.Payment(payment);
var response = storeController.GeneralSettings();
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model;
modify(settings);
await storeController.GeneralSettings(settings);
}
public async Task ModifyWalletSettings(Action<WalletSettingsViewModel> modify)

View file

@ -275,13 +275,13 @@ namespace BTCPayServer.Tests
// Set tolerance to 50%
var stores = user.GetController<UIStoresController>();
var response = stores.Payment();
var vm = Assert.IsType<PaymentViewModel>(Assert.IsType<ViewResult>(response).Model);
var response = stores.GeneralSettings();
var vm = Assert.IsType<GeneralSettingsViewModel>(Assert.IsType<ViewResult>(response).Model);
Assert.Equal(0.0, vm.PaymentTolerance);
vm.PaymentTolerance = 50.0;
Assert.IsType<RedirectToActionResult>(stores.Payment(vm).Result);
Assert.IsType<RedirectToActionResult>(stores.GeneralSettings(vm).Result);
var invoice = user.BitPay.CreateInvoice(
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },
@ -295,7 +295,7 @@ namespace BTCPayServer.Tests
// Pays 75%
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress,
await tester.ExplorerNode.SendToAddressAsync(invoiceAddress,
Money.Satoshis(invoice.BtcDue.Satoshi * 0.75m));
TestUtils.Eventually(() =>
@ -415,13 +415,13 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
await user.GrantAccessAsync(true);
var storeController = user.GetController<UIStoresController>();
var storeResponse = storeController.Payment();
var storeResponse = storeController.GeneralSettings();
Assert.IsType<ViewResult>(storeResponse);
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC"));
var testResult = storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
storeController.SetupLightningNode(user.StoreId, new LightningNodeViewModel
{
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(

View file

@ -35,7 +35,7 @@
</a>
</li>
<li class="nav-item">
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.Payment) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.General) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<a asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.General) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/>
<span>Settings</span>
</a>

View file

@ -101,7 +101,7 @@ namespace BTCPayServer.Controllers
{
StoreId = store.Id,
StoreName = store.StoreName,
StoreLink = Url.Action(nameof(UIStoresController.Payment), "UIStores", new { storeId = store.Id }),
StoreLink = Url.Action(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId = store.Id }),
PaymentRequestLink = Url.Action(nameof(UIPaymentRequestController.ViewPaymentRequest), "UIPaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }),
Id = invoice.Id,
State = invoiceState,
@ -899,7 +899,7 @@ namespace BTCPayServer.Controllers
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"To create an invoice, you need to <a href='{Url.Action(nameof(UIStoresController.Payment), "UIStores", new { storeId = store.Id })}' class='alert-link'>set up a payment method</a> first",
Html = $"To create an invoice, you need to <a href='{Url.Action(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId = store.Id })}' class='alert-link'>set up a payment method</a> first",
AllowDismiss = false
});
return View(model);

View file

@ -158,11 +158,7 @@ namespace BTCPayServer.Controllers
switch (command)
{
case "save":
var storeBlob = store.GetStoreBlob();
storeBlob.Hints.Lightning = false;
var lnurl = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
store.SetStoreBlob(storeBlob);
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
store.SetSupportedPaymentMethod(lnurl, new LNURLPaySupportedPaymentMethod()
{

View file

@ -163,7 +163,6 @@ namespace BTCPayServer.Controllers
await wallet.TrackAsync(strategy.AccountDerivation);
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
storeBlob.SetExcluded(paymentMethodId, false);
storeBlob.Hints.Wallet = false;
storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false);
store.SetStoreBlob(storeBlob);
}
@ -362,7 +361,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated.";
return RedirectToAction(nameof(Payment), new { storeId });
return RedirectToAction(nameof(GeneralSettings), new { storeId });
}
[HttpGet("{storeId}/onchain/{cryptoCode}/settings")]
@ -734,7 +733,7 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] =
$"On-Chain payment for {network.CryptoCode} has been removed.";
return RedirectToAction(nameof(Payment), new { storeId });
return RedirectToAction(nameof(GeneralSettings), new { storeId });
}
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)

View file

@ -104,7 +104,6 @@ namespace BTCPayServer.Controllers
private readonly EventAggregator _EventAggregator;
private readonly NBXplorerDashboard _Dashboard;
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
public string CreatedStoreId { get; set; }
[TempData]
public bool StoreNotConfigured
@ -589,58 +588,6 @@ namespace BTCPayServer.Controllers
}
}
[HttpGet("{storeId}/payment")]
public IActionResult Payment()
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new PaymentViewModel
{
Id = store.Id,
NetworkFeeMode = storeBlob.NetworkFeeMode,
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
PaymentTolerance = storeBlob.PaymentTolerance,
InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes,
DefaultCurrency = storeBlob.DefaultCurrency,
BOLT11Expiration = (long)storeBlob.RefundBOLT11Expiration.TotalDays
};
return View(vm);
}
[HttpPost("{storeId}/payment")]
public async Task<IActionResult> Payment(PaymentViewModel model, string command = null)
{
bool needUpdate = false;
var blob = CurrentStore.GetStoreBlob();
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
blob.NetworkFeeMode = model.NetworkFeeMode;
blob.PaymentTolerance = model.PaymentTolerance;
blob.DefaultCurrency = model.DefaultCurrency;
blob.InvoiceExpiration = TimeSpan.FromMinutes(model.InvoiceExpiration);
blob.RefundBOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration);
if (CurrentStore.SetStoreBlob(blob))
{
needUpdate = true;
}
if (needUpdate)
{
await _Repo.UpdateStore(CurrentStore);
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
}
return RedirectToAction(nameof(Payment), new
{
storeId = CurrentStore.Id
});
}
[HttpGet("{storeId}/settings")]
public IActionResult GeneralSettings()
{
@ -648,11 +595,18 @@ namespace BTCPayServer.Controllers
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new GeneralSettingsViewModel
{
Id = store.Id,
StoreName = store.StoreName,
StoreWebsite = store.StoreWebsite,
NetworkFeeMode = storeBlob.NetworkFeeMode,
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
PaymentTolerance = storeBlob.PaymentTolerance,
InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes,
DefaultCurrency = storeBlob.DefaultCurrency,
BOLT11Expiration = (long)storeBlob.RefundBOLT11Expiration.TotalDays,
CanDelete = _Repo.CanDeleteStores()
};
@ -674,6 +628,19 @@ namespace BTCPayServer.Controllers
needUpdate = true;
CurrentStore.StoreWebsite = model.StoreWebsite;
}
var blob = CurrentStore.GetStoreBlob();
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
blob.NetworkFeeMode = model.NetworkFeeMode;
blob.PaymentTolerance = model.PaymentTolerance;
blob.DefaultCurrency = model.DefaultCurrency;
blob.InvoiceExpiration = TimeSpan.FromMinutes(model.InvoiceExpiration);
blob.RefundBOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration);
if (CurrentStore.SetStoreBlob(blob))
{
needUpdate = true;
}
if (needUpdate)
{
@ -1002,7 +969,7 @@ namespace BTCPayServer.Controllers
CurrentStore.SetStoreBlob(blob);
TempData[WellKnownTempData.SuccessMessage] = "Feature disabled";
await _Repo.UpdateStore(CurrentStore);
return RedirectToAction(nameof(Payment), new { storeId = storeId });
return RedirectToAction(nameof(GeneralSettings), new { storeId });
}
[Route("{storeId}/paybutton")]

View file

@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
@ -5,12 +7,13 @@ using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Security;
using BTCPayServer.Rating;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using ExchangeSharp;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Controllers
{
@ -20,20 +23,30 @@ namespace BTCPayServer.Controllers
{
private readonly StoreRepository _repo;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RateFetcher _rateFactory;
public string CreatedStoreId { get; set; }
public UIUserStoresController(
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository)
StoreRepository storeRepository,
RateFetcher rateFactory)
{
_repo = storeRepository;
_userManager = userManager;
_rateFactory = rateFactory;
}
[HttpGet("create")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
public IActionResult CreateStore()
{
return View();
var vm = new CreateStoreViewModel
{
DefaultCurrency = StoreBlob.StandardDefaultCurrency,
Exchanges = GetExchangesSelectList(CoinGeckoRateProvider.CoinGeckoName)
};
return View(vm);
}
[HttpPost("create")]
@ -42,10 +55,13 @@ namespace BTCPayServer.Controllers
{
if (!ModelState.IsValid)
{
vm.Exchanges = GetExchangesSelectList(vm.PreferredExchange);
return View(vm);
}
var store = await _repo.CreateStore(GetUserId(), vm.Name);
var store = await _repo.CreateStore(GetUserId(), vm.Name, vm.DefaultCurrency, vm.PreferredExchange);
CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new
{
@ -53,11 +69,6 @@ namespace BTCPayServer.Controllers
});
}
public string CreatedStoreId
{
get; set;
}
[HttpGet("{storeId}/me/delete")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public IActionResult DeleteStore(string storeId)
@ -82,5 +93,14 @@ namespace BTCPayServer.Controllers
}
private string GetUserId() => _userManager.GetUserId(User);
private SelectList GetExchangesSelectList(string selected) {
var exchanges = _rateFactory.RateProviderFactory
.GetSupportedExchanges()
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase);
var chosen = exchanges.FirstOrDefault(f => f.Id == selected) ?? exchanges.First();
return new SelectList(exchanges, nameof(chosen.Id), nameof(chosen.Name), chosen.Id);
}
}
}

View file

@ -18,6 +18,8 @@ namespace BTCPayServer.Data
{
public class StoreBlob
{
public static string StandardDefaultCurrency = "USD";
public StoreBlob()
{
InvoiceExpiration = TimeSpan.FromMinutes(15);
@ -27,8 +29,7 @@ namespace BTCPayServer.Data
RecommendedFeeBlockTarget = 1;
PaymentMethodCriteria = new List<PaymentMethodCriteria>();
}
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public NetworkFeeMode NetworkFeeMode { get; set; }
@ -45,7 +46,7 @@ namespace BTCPayServer.Data
{
get
{
return string.IsNullOrEmpty(_DefaultCurrency) ? "USD" : _DefaultCurrency;
return string.IsNullOrEmpty(_DefaultCurrency) ? StandardDefaultCurrency : _DefaultCurrency;
}
set
{
@ -169,8 +170,6 @@ namespace BTCPayServer.Data
public EmailSettings EmailSettings { get; set; }
public bool PayJoinEnabled { get; set; }
public StoreHints Hints { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
@ -179,12 +178,6 @@ namespace BTCPayServer.Data
[JsonConverter(typeof(TimeSpanJsonConverter.Days))]
public TimeSpan RefundBOLT11Expiration { get; set; }
public class StoreHints
{
public bool Wallet { get; set; }
public bool Lightning { get; set; }
}
public IPaymentFilter GetExcludedPaymentMethods()
{
#pragma warning disable CS0618 // Type or member is obsolete

View file

@ -50,9 +50,6 @@ namespace BTCPayServer.Data
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
if (result.PreferredExchange == null)
result.PreferredExchange = CoinGeckoRateProvider.CoinGeckoName;
if (result.Hints == null)
result.Hints = new StoreBlob.StoreHints();
return result;
}

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Models.StoreViewModels
{
@ -7,9 +8,17 @@ namespace BTCPayServer.Models.StoreViewModels
[Required]
[MaxLength(50)]
[MinLength(1)]
public string Name
{
get; set;
}
public string Name { get; set; }
[Required]
[MaxLength(10)]
[Display(Name = "Default currency")]
public string DefaultCurrency { get; set; }
[Required]
[Display(Name = "Preferred Price Source")]
public string PreferredExchange { get; set; }
public SelectList Exchanges { get; set; }
}
}

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using BTCPayServer.Validation;
namespace BTCPayServer.Models.StoreViewModels
@ -21,5 +22,27 @@ namespace BTCPayServer.Models.StoreViewModels
public string StoreWebsite { get; set; }
public bool CanDelete { get; set; }
[Display(Name = "Allow anyone to create invoice")]
public bool AnyoneCanCreateInvoice { get; set; }
[Display(Name = "Invoice expires if the full amount has not been paid after …")]
[Range(1, 60 * 24 * 24)]
public int InvoiceExpiration { get; set; }
[Display(Name = "Add additional fee (network fee) to invoice …")]
public NetworkFeeMode NetworkFeeMode { get; set; }
[Display(Name = "Consider the invoice paid even if the paid amount is ... % less than expected")]
[Range(0, 100)]
public double PaymentTolerance { get; set; }
[Display(Name = "Default currency")]
[MaxLength(10)]
public string DefaultCurrency { get; set; }
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
[Range(1, 365 * 10)]
public long BOLT11Expiration { get; set; }
}
}

View file

@ -16,27 +16,15 @@ namespace BTCPayServer.Models.StoreViewModels
public string Rule { get; set; }
public bool Error { get; set; }
}
public void SetExchangeRates(IEnumerable<AvailableRateProvider> supportedList, string preferredExchange)
{
var defaultStore = preferredExchange ?? CoinGeckoRateProvider.CoinGeckoName;
supportedList = supportedList.Select(a => new AvailableRateProvider(a.Id, a.SourceId, GetName(a), a.Url, a.Source)).ToArray();
supportedList = supportedList.Select(a => new AvailableRateProvider(a.Id, a.SourceId, a.DisplayName, a.Url, a.Source)).ToArray();
var chosen = supportedList.FirstOrDefault(f => f.Id == defaultStore) ?? supportedList.FirstOrDefault();
Exchanges = new SelectList(supportedList, nameof(chosen.Id), nameof(chosen.Name), chosen);
PreferredExchange = chosen.Id;
RateSource = chosen.Url;
}
private string GetName(AvailableRateProvider a)
{
switch (a.Source)
{
case Rating.RateSource.Direct:
return a.Name;
case Rating.RateSource.Coingecko:
return $"{a.Name} (via CoinGecko)";
default:
throw new NotSupportedException(a.Source.ToString());
}
PreferredExchange = chosen?.Id;
RateSource = chosen?.Url;
}
public List<TestResultViewModel> TestRateRules { get; set; }
@ -56,19 +44,11 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Add Exchange Rate Spread")]
[Range(0.0, 100.0)]
public double Spread
{
get;
set;
}
public double Spread { get; set; }
[Display(Name = "Preferred Price Source")]
public string PreferredExchange { get; set; }
public string RateSource
{
get;
set;
}
public string RateSource { get; set; }
}
}

View file

@ -152,17 +152,6 @@ namespace BTCPayServer.Services.Stores
}
}
private void SetNewStoreHints(ref StoreData storeData)
{
var blob = storeData.GetStoreBlob();
blob.Hints = new Data.StoreBlob.StoreHints
{
Wallet = true,
Lightning = true
};
storeData.SetStoreBlob(blob);
}
public async Task CreateStore(string ownerId, StoreData storeData)
{
if (!string.IsNullOrEmpty(storeData.Id))
@ -179,17 +168,19 @@ namespace BTCPayServer.Services.Stores
Role = StoreRoles.Owner,
};
SetNewStoreHints(ref storeData);
ctx.Add(storeData);
ctx.Add(userStore);
await ctx.SaveChangesAsync();
}
public async Task<StoreData> CreateStore(string ownerId, string name)
public async Task<StoreData> CreateStore(string ownerId, string name, string defaultCurrency, string preferredExchange)
{
var store = new StoreData() { StoreName = name };
SetNewStoreHints(ref store);
var store = new StoreData { StoreName = name };
var blob = store.GetStoreBlob();
blob.DefaultCurrency = defaultCurrency;
blob.PreferredExchange = preferredExchange;
store.SetStoreBlob(blob);
await CreateStore(ownerId, store);
return store;
}

View file

@ -2,12 +2,13 @@
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@{
var store = Context.GetStoreData();
var cryptoCode = "BTC";
var isLightningEnabled = store.IsLightningEnabled(BTCPayNetworkProvider);
var isLNUrlEnabled = store.IsLNUrlEnabled(BTCPayNetworkProvider);
var possible =
isLightningEnabled &&
isLNUrlEnabled &&
store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().Any(type => type.CryptoCode == "BTC");
store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().Any(type => type.CryptoCode == cryptoCode);
}
<li class="list-group-item bg-tile" id="lightning-address-option">
<div class="d-flex align-items-center">
@ -30,7 +31,7 @@
{
if (!isLightningEnabled)
{
<a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@store.Id" class="btn btn-link p-0">
<a asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-cryptoCode="@cryptoCode" asp-route-storeId="@store.Id" class="btn btn-link p-0">
You need to setup Lightning first
</a>
}

View file

@ -79,7 +79,7 @@
<td>
@if (app.IsOwner)
{
<span><a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@app.StoreId">@app.StoreName</a></span>
<span><a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@app.StoreId">@app.StoreName</a></span>
}
else
{

View file

@ -24,14 +24,13 @@
{
supported = null;
}
}
@if (supported is null)
{
<div class="alert alert-warning text-center sticky-top mb-0 rounded-0" role="alert">
LNURL is not enabled on your store, which this print feature relies on.
<a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@Model.Store.Id" class="alert-link p-0">
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="alert-link p-0">
Enable LNURL
</a>
</div>

View file

@ -63,7 +63,7 @@ else
}
else
{
<a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Model.StoreId" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.StoreId" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a wallet</h5>
@ -95,7 +95,7 @@ else
}
else
{
<a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Model.StoreId" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.StoreId" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a Lightning node</h5>

View file

@ -5,7 +5,7 @@
}
<div class="row">
<div class="col-lg-8">
<div class="col-lg-10 col-xl-8">
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All" class="text-danger"></div>
@ -27,8 +27,64 @@
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
</div>
<h3 class="mt-5 mb-3">Payment</h3>
<div class="form-group">
<label asp-for="DefaultCurrency" class="form-label"></label>
<input asp-for="DefaultCurrency" class="form-control" currency-selection style="max-width:10ch;" />
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
<div class="form-group d-flex align-items-center">
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</div>
<div class="form-group mt-4">
<label asp-for="NetworkFeeMode" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<select asp-for="NetworkFeeMode" class="form-select">
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
<option value="Always">... on every payment</option>
<option value="Never">Never add network fee</option>
</select>
</div>
<div class="form-group">
<label asp-for="InvoiceExpiration" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">minutes</span>
</div>
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PaymentTolerance" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">percent</span>
</div>
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BOLT11Expiration" class="form-label"></label>
<div class="input-group">
<input asp-for="BOLT11Expiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">days</span>
</div>
<span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
</div>
<button name="command" type="submit" class="btn btn-primary mt-2" value="Save" id="Save">Save</button>
</form>
<h3 class="mt-5 mb-3">Services</h3>
<div class="table-responsive-md">
<table class="table table-hover mt-1 mb-5">

View file

@ -1,75 +0,0 @@
@model PaymentViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Payment, "Wallets", Context.GetStoreData().Id);
}
<div class="row">
<div class="col-lg-10 col-xl-9">
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All" class="text-danger"></div>
}
<form method="post">
<h3 class="mb-3">Payment</h3>
<div class="form-group">
<label asp-for="DefaultCurrency" class="form-label"></label>
<input asp-for="DefaultCurrency" currency-selection class="form-control" style="max-width:10ch;" />
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
<div class="form-group d-flex align-items-center">
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</div>
<div class="form-group mt-4">
<label asp-for="NetworkFeeMode" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<select asp-for="NetworkFeeMode" class="form-select w-auto">
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
<option value="Always">... on every payment</option>
<option value="Never">Never add network fee</option>
</select>
</div>
<div class="form-group">
<label asp-for="InvoiceExpiration" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">minutes</span>
</div>
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PaymentTolerance" class="form-label"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<div class="input-group">
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">percent</span>
</div>
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="BOLT11Expiration" class="form-label"></label>
<div class="input-group">
<input asp-for="BOLT11Expiration" class="form-control" style="max-width:10ch;"/>
<span class="input-group-text">days</span>
</div>
<span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
</div>
<button name="command" type="submit" class="btn btn-primary px-4 mt-3" value="Save" id="Save">Save</button>
</form>
</div>
</div>
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />
}

View file

@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Stores
{
public enum StoreNavPages
{
Create, Dashboard, Rates, Payment, OnchainSettings, LightningSettings, Lightning, CheckoutAppearance, General, Tokens, Users, PayButton, Integrations, Webhooks, PullPayments, Payouts
Create, Dashboard, General, Rates, OnchainSettings, LightningSettings, Lightning, CheckoutAppearance, Tokens, Users, PayButton, Integrations, Webhooks, PullPayments, Payouts
}
}

View file

@ -13,7 +13,7 @@
@section Navbar {
@await RenderSectionAsync("Navbar", false)
<a asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Context.GetRouteValue("storeId")" class="cancel">
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")" class="cancel">
<vc:icon symbol="close" />
</a>
}

View file

@ -3,7 +3,6 @@
<nav id="SectionNav">
<div class="nav">
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.General))" class="nav-link @ViewData.IsActivePage(StoreNavPages.General)" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Payment))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payment)" asp-controller="UIStores" asp-action="Payment" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout Appearance</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a>

View file

@ -19,6 +19,16 @@
<input asp-for="Name" class="form-control" required />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DefaultCurrency" class="form-label" data-required></label>
<input asp-for="DefaultCurrency" class="form-control" currency-selection style="max-width:10ch;" />
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PreferredExchange" class="form-label" data-required></label>
<select asp-for="PreferredExchange" asp-items="Model.Exchanges" class="form-select w-auto"></select>
<span asp-validation-for="PreferredExchange" class="text-danger"></span>
</div>
<div class="form-group mt-4">
<input type="submit" value="Create" class="btn btn-primary" id="Create" />
</div>

View file

@ -44,7 +44,7 @@
<tr>
@if (wallet.IsOwner)
{
<td><a asp-action="Payment" asp-controller="UIStores" asp-route-storeId="@wallet.StoreId">@wallet.StoreName</a></td>
<td><a asp-action="GeneralSettings" asp-controller="UIStores" asp-route-storeId="@wallet.StoreId">@wallet.StoreName</a></td>
}
else
{