mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
Create separate payment settings section for stores
This commit is contained in:
parent
8c2dcfa166
commit
f28eb13b0d
15 changed files with 381 additions and 183 deletions
|
@ -77,7 +77,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Get enabled state from overview action
|
||||
StoreViewModel storeModel;
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var lnNode = storeModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
|
||||
Assert.NotNull(lnNode);
|
||||
|
@ -89,7 +89,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
|
@ -98,7 +98,7 @@ namespace BTCPayServer.Tests
|
|||
// Disable wallet
|
||||
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
|
|
|
@ -137,10 +137,10 @@ namespace BTCPayServer.Tests
|
|||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.AddLightningNode();
|
||||
s.GoToStore(store.storeId);
|
||||
s.GoToStore(store.storeId, StoreNavPages.Payment);
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
Assert.Contains("Payment settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
|
|
|
@ -165,7 +165,7 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
|
|
|
@ -570,9 +570,9 @@ namespace BTCPayServer.Tests
|
|||
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
||||
await notifications.NextEventAsync();
|
||||
await bob.ModifyStore(s => s.PayJoinEnabled = true);
|
||||
await bob.ModifyPayment(p => p.PayJoinEnabled = true);
|
||||
var invoice = bob.BitPay.CreateInvoice(
|
||||
new Invoice() { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await RegisterAsync(isAdmin);
|
||||
await CreateStoreAsync();
|
||||
var store = this.GetController<StoresController>();
|
||||
var store = GetController<StoresController>();
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
|
@ -127,19 +127,28 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public async Task SetNetworkFeeMode(NetworkFeeMode mode)
|
||||
{
|
||||
await ModifyStore(store =>
|
||||
await ModifyPayment(payment =>
|
||||
{
|
||||
store.NetworkFeeMode = mode;
|
||||
payment.NetworkFeeMode = mode;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ModifyStore(Action<StoreViewModel> modify)
|
||||
public void ModifyStore(Action<StoreViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.UpdateStore();
|
||||
var response = storeController.UpdateStore();
|
||||
StoreViewModel store = (StoreViewModel)((ViewResult)response).Model;
|
||||
modify(store);
|
||||
storeController.UpdateStore(store).GetAwaiter().GetResult();
|
||||
storeController.UpdateStore(store);
|
||||
}
|
||||
|
||||
public async Task ModifyPayment(Action<PaymentViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.Payment();
|
||||
PaymentViewModel payment = (PaymentViewModel)((ViewResult)response).Model;
|
||||
modify(payment);
|
||||
storeController.Payment(payment).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
|
@ -190,7 +199,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public Task EnablePayJoin()
|
||||
{
|
||||
return ModifyStore(s => s.PayJoinEnabled = true);
|
||||
return ModifyPayment(p => p.PayJoinEnabled = true);
|
||||
}
|
||||
|
||||
public GenerateWalletResponse GenerateWalletResponseV { get; set; }
|
||||
|
|
|
@ -818,11 +818,11 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Set tolerance to 50%
|
||||
var stores = user.GetController<StoresController>();
|
||||
var response = await stores.UpdateStore();
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
var response = await stores.Payment();
|
||||
var vm = Assert.IsType<PaymentViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
Assert.Equal(0.0, vm.PaymentTolerance);
|
||||
vm.PaymentTolerance = 50.0;
|
||||
Assert.IsType<RedirectToActionResult>(stores.UpdateStore(vm).Result);
|
||||
Assert.IsType<RedirectToActionResult>(stores.Payment(vm).Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
|
@ -996,8 +996,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(4, tor.Services.Length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
|
@ -1012,7 +1011,7 @@ namespace BTCPayServer.Tests
|
|||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
await user.ModifyStore(model => model.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
await user.ModifyPayment(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||
{
|
||||
|
@ -1065,7 +1064,7 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var storeResponse = await storeController.UpdateStore();
|
||||
var storeResponse = storeController.UpdateStore();
|
||||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
|
@ -1089,7 +1088,7 @@ namespace BTCPayServer.Tests
|
|||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
storeResponse = await storeController.UpdateStore();
|
||||
storeResponse = storeController.UpdateStore();
|
||||
var storeVm =
|
||||
Assert.IsType<StoreViewModel>(Assert
|
||||
.IsType<ViewResult>(storeResponse).Model);
|
||||
|
@ -1205,7 +1204,7 @@ namespace BTCPayServer.Tests
|
|||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
await acc.ModifyStore(s => s.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
await acc.ModifyPayment(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||
{
|
||||
Price = 5.0m,
|
||||
|
@ -2032,7 +2031,7 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
Assert.Equal(404, (int)response.StatusCode);
|
||||
|
||||
await user.ModifyStore(s => s.AnyoneCanCreateInvoice = true);
|
||||
await user.ModifyPayment(p => p.AnyoneCanCreateInvoice = true);
|
||||
|
||||
Logs.Tester.LogInformation("Bad store with anyone can create invoice = 403");
|
||||
response = await tester.PayTester.HttpClient.SendAsync(
|
||||
|
@ -2448,12 +2447,12 @@ namespace BTCPayServer.Tests
|
|||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
|
||||
// enable unified QR code in settings
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert
|
||||
.IsType<ViewResult>(await user.GetController<StoresController>().UpdateStore()).Model
|
||||
var vm = Assert.IsType<PaymentViewModel>(Assert
|
||||
.IsType<ViewResult>(await user.GetController<StoresController>().Payment()).Model
|
||||
);
|
||||
vm.OnChainWithLnInvoiceFallback = true;
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
user.GetController<StoresController>().UpdateStore(vm).Result
|
||||
user.GetController<StoresController>().Payment(vm).Result
|
||||
);
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
|
@ -2470,7 +2469,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
|
||||
// Fallback lightning invoice should be uppercase inside the QR code.
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new string[] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new [] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
Assert.True(lightningFallback.ToUpperInvariant() == lightningFallback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -493,8 +493,8 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, StoreViewModel vm)
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob,
|
||||
out List<StoreDerivationScheme> derivationSchemes, out List<StoreLightningNode> lightningNodes)
|
||||
{
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var derivationByCryptoCode =
|
||||
|
@ -509,6 +509,9 @@ namespace BTCPayServer.Controllers
|
|||
.Where(method => method.PaymentId.PaymentType == LightningPaymentType.Instance)
|
||||
.ToDictionary(c => c.CryptoCode.ToUpperInvariant());
|
||||
|
||||
derivationSchemes = new List<StoreDerivationScheme>();
|
||||
lightningNodes = new List<StoreLightningNode>();
|
||||
|
||||
foreach (var paymentMethodId in _paymentMethodHandlerDictionary.Distinct().SelectMany(handler => handler.GetSupportedPaymentMethods()))
|
||||
{
|
||||
switch (paymentMethodId.PaymentType)
|
||||
|
@ -518,7 +521,7 @@ namespace BTCPayServer.Controllers
|
|||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||
|
||||
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
||||
derivationSchemes.Add(new StoreDerivationScheme
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
WalletSupported = network.WalletSupported,
|
||||
|
@ -530,12 +533,14 @@ namespace BTCPayServer.Controllers
|
|||
#endif
|
||||
});
|
||||
break;
|
||||
|
||||
case LNURLPayPaymentType lnurlPayPaymentType:
|
||||
break;
|
||||
|
||||
case LightningPaymentType _:
|
||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var isEnabled = !excludeFilters.Match(paymentMethodId) && lightning != null;
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode
|
||||
lightningNodes.Add(new StoreLightningNode
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
|
@ -547,30 +552,92 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
[HttpGet("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore()
|
||||
public IActionResult UpdateStore()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.DefaultCurrency = storeBlob.DefaultCurrency;
|
||||
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
||||
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.CanDelete = _Repo.CanDeleteStores();
|
||||
AddPaymentMethods(store, storeBlob, vm);
|
||||
var vm = new StoreViewModel
|
||||
{
|
||||
Id = store.Id,
|
||||
CanDelete = _Repo.CanDeleteStores(),
|
||||
StoreName = store.StoreName,
|
||||
StoreWebsite = store.StoreWebsite,
|
||||
HintWallet = storeBlob.Hints.Wallet,
|
||||
HintLightning = storeBlob.Hints.Lightning
|
||||
};
|
||||
|
||||
AddPaymentMethods(store, storeBlob,
|
||||
out var derivationSchemes, out var lightningNodes);
|
||||
|
||||
vm.DerivationSchemes = derivationSchemes;
|
||||
vm.LightningNodes = lightningNodes;
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (CurrentStore.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreName = model.StoreName;
|
||||
}
|
||||
if (CurrentStore.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/payment")]
|
||||
public async Task<IActionResult> Payment()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new PaymentViewModel
|
||||
{
|
||||
NetworkFeeMode = storeBlob.NetworkFeeMode,
|
||||
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
|
||||
SpeedPolicy = store.SpeedPolicy,
|
||||
PaymentTolerance = storeBlob.PaymentTolerance,
|
||||
DefaultCurrency = storeBlob.DefaultCurrency
|
||||
};
|
||||
|
||||
AddPaymentMethods(store, storeBlob,
|
||||
out var derivationSchemes, out var lightningNodes);
|
||||
|
||||
vm.DerivationSchemes = derivationSchemes;
|
||||
vm.LightningNodes = lightningNodes;
|
||||
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
|
||||
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
|
||||
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
||||
vm.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
||||
vm.HintWallet = storeBlob.Hints.Wallet;
|
||||
vm.HintLightning = storeBlob.Hints.Lightning;
|
||||
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
vm.LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints;
|
||||
vm.OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback;
|
||||
|
@ -584,12 +651,12 @@ namespace BTCPayServer.Controllers
|
|||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet);
|
||||
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||
[HttpPost("{storeId}/payment")]
|
||||
public async Task<IActionResult> Payment(PaymentViewModel model, string command = null)
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (CurrentStore.SpeedPolicy != model.SpeedPolicy)
|
||||
|
@ -597,16 +664,6 @@ namespace BTCPayServer.Controllers
|
|||
needUpdate = true;
|
||||
CurrentStore.SpeedPolicy = model.SpeedPolicy;
|
||||
}
|
||||
if (CurrentStore.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreName = model.StoreName;
|
||||
}
|
||||
if (CurrentStore.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
blob.DefaultCurrency = model.DefaultCurrency;
|
||||
|
@ -633,7 +690,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
||||
|
||||
if (payjoinChanged && blob.PayJoinEnabled)
|
||||
{
|
||||
|
@ -649,13 +706,13 @@ namespace BTCPayServer.Controllers
|
|||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = $"The store was updated successfully. However, payjoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
Html = $"The payment settings were updated successfully. However, payjoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
return RedirectToAction(nameof(Payment), new
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
|
|
64
BTCPayServer/Models/StoreViewModels/PaymentViewModel.cs
Normal file
64
BTCPayServer/Models/StoreViewModels/PaymentViewModel.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using static BTCPayServer.Data.StoreBlob;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class PaymentViewModel
|
||||
{
|
||||
public List<StoreDerivationScheme> DerivationSchemes { get; set; }
|
||||
public List<StoreLightningNode> LightningNodes { get; set; }
|
||||
public bool IsOnchainSetup { get; set; }
|
||||
public bool IsLightningSetup { get; set; }
|
||||
public bool CanUsePayJoin { 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 = "Payment invalid if transactions fails to confirm … after invoice expiration")]
|
||||
[Range(10, 60 * 24 * 24)]
|
||||
public int MonitoringExpiration { get; set; }
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction …")]
|
||||
public SpeedPolicy SpeedPolicy { get; set; }
|
||||
|
||||
[Display(Name = "Add additional fee (network fee) to invoice …")]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; }
|
||||
|
||||
[Display(Name = "Description template of the lightning invoice")]
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
|
||||
[Display(Name = "Enable Payjoin/P2EP")]
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
|
||||
[Display(Name = "Show recommended fee")]
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
[Display(Name = "Recommended fee confirmation target blocks")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
[Display(Name = "Display Lightning payment amounts in Satoshis")]
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
[Display(Name = "Add hop hints for private channels to the Lightning invoice")]
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
[Display(Name = "Include Lightning invoice fallback to on-chain BIP21 payment URL")]
|
||||
public bool OnChainWithLnInvoiceFallback { 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; }
|
||||
}
|
||||
}
|
12
BTCPayServer/Models/StoreViewModels/StoreDerivationScheme.cs
Normal file
12
BTCPayServer/Models/StoreViewModels/StoreDerivationScheme.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoreDerivationScheme
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool WalletSupported { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Collapsed { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoreLightningNode
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,130 +8,24 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
{
|
||||
public class StoreViewModel
|
||||
{
|
||||
public class DerivationScheme
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool WalletSupported { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Collapsed { get; set; }
|
||||
}
|
||||
|
||||
public class AdditionalPaymentMethod
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string Action { get; set; }
|
||||
}
|
||||
public StoreViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public List<StoreDerivationScheme> DerivationSchemes { get; set; }
|
||||
public List<StoreLightningNode> LightningNodes { get; set; }
|
||||
public bool HintWallet { get; set; }
|
||||
public bool HintLightning { get; set; }
|
||||
public bool CanDelete { get; set; }
|
||||
|
||||
[Display(Name = "Store ID")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Store Name")]
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[MinLength(1)]
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreName { get; set; }
|
||||
|
||||
[Uri]
|
||||
[Display(Name = "Store Website")]
|
||||
[MaxLength(500)]
|
||||
public string StoreWebsite
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Default currency")]
|
||||
[MaxLength(10)]
|
||||
public string DefaultCurrency { get; set; }
|
||||
|
||||
[Display(Name = "Allow anyone to create invoice")]
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
|
||||
|
||||
[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 = "Payment invalid if transactions fails to confirm … after invoice expiration")]
|
||||
[Range(10, 60 * 24 * 24)]
|
||||
public int MonitoringExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction …")]
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Add additional fee (network fee) to invoice …")]
|
||||
public NetworkFeeMode NetworkFeeMode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Description template of the lightning invoice")]
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
|
||||
[Display(Name = "Enable Payjoin/P2EP")]
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
public bool CanUsePayJoin { get; set; }
|
||||
public bool IsOnchainSetup { get; set; }
|
||||
public bool IsLightningSetup { get; set; }
|
||||
|
||||
public bool HintWallet { get; set; }
|
||||
public bool HintLightning { get; set; }
|
||||
|
||||
[Display(Name = "Show recommended fee")]
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
[Display(Name = "Recommended fee confirmation target blocks")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
[Display(Name = "Display Lightning payment amounts in Satoshis")]
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
[Display(Name = "Add hop hints for private channels to the Lightning invoice")]
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
[Display(Name = "Include Lightning invoice fallback to on-chain BIP21 payment URL")]
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
|
||||
public class LightningNode
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
public List<LightningNode> LightningNodes
|
||||
{
|
||||
get; set;
|
||||
} = new List<LightningNode>();
|
||||
|
||||
[Display(Name = "Consider the invoice paid even if the paid amount is ... % less than expected")]
|
||||
[Range(0, 100)]
|
||||
public double PaymentTolerance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string StoreWebsite { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
<h4 class="mb-3">Payment</h4>
|
||||
<h4 class="mb-3">Invoice Settings</h4>
|
||||
@if (Model.PaymentMethods.Any())
|
||||
{
|
||||
<div class="form-group mb-4">
|
||||
|
|
156
BTCPayServer/Views/Stores/Payment.cshtml
Normal file
156
BTCPayServer/Views/Stores/Payment.cshtml
Normal file
|
@ -0,0 +1,156 @@
|
|||
@model PaymentViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.Payment, "Payment", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-10 col-xl-9">
|
||||
<h4 class="mb-3">Payment</h4>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
@if (Model.IsOnchainSetup || Model.IsLightningSetup)
|
||||
{
|
||||
<form method="post">
|
||||
<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="DefaultCurrency" class="form-label"></label>
|
||||
<input asp-for="DefaultCurrency" class="form-control" />
|
||||
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
|
||||
</div>
|
||||
@if (Model.IsOnchainSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">On-Chain</h5>
|
||||
@if (Model.CanUsePayJoin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input asp-for="PayJoinEnabled" type="checkbox" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="PayJoinEnabled" class="form-label mb-0 me-1"></label>
|
||||
<a href="https://docs.btcpayserver.org/Payjoin/" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="MonitoringExpiration" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration" 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="MonitoringExpiration" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">minutes</span>
|
||||
</div>
|
||||
<span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<select asp-for="SpeedPolicy" class="form-select w-auto" onchange="document.getElementById('unconfirmed-warning').hidden = this.value !== '0';">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="3">Has at least 2 confirmations</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<div class="alert alert-warning my-2" hidden="@(Model.SpeedPolicy != 0)" id="unconfirmed-warning" role="alert">
|
||||
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
|
||||
</div>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="ShowRecommendedFee" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
|
||||
<p class="form-text text-muted mb-0">Fee will be shown for BTC and LTC onchain payments only.</p>
|
||||
</div>
|
||||
<div class="form-group mt-2 mb-4">
|
||||
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
|
||||
<input asp-for="RecommendedFeeBlockTarget" class="form-control" style="width:8ch" min="1" />
|
||||
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.IsLightningSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">Lightning</h5>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningAmountInSatoshi" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="LightningAmountInSatoshi" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningPrivateRouteHints" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="LightningPrivateRouteHints" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="OnChainWithLnInvoiceFallback" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="OnChainWithLnInvoiceFallback" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label asp-for="LightningDescriptionTemplate" class="form-label"></label>
|
||||
<input asp-for="LightningDescriptionTemplate" class="form-control"/>
|
||||
<span asp-validation-for="LightningDescriptionTemplate" class="text-danger"></span>
|
||||
<p class="form-text text-muted">
|
||||
Available placeholders:
|
||||
<code>{StoreName} {ItemDescription} {OrderId}</code>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save" id="Save">Save Payment Settings</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
Please configure either an on-chain wallet or Lightning node first.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
|
@ -2,8 +2,6 @@ namespace BTCPayServer.Views.Stores
|
|||
{
|
||||
public enum StoreNavPages
|
||||
{
|
||||
Index, Create, Rates, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage,
|
||||
PullPayments,
|
||||
Payouts
|
||||
Index, Create, Rates, Payment, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage, PullPayments, Payouts
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pay Button</a>
|
||||
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Integrations</a>
|
||||
<a id="@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Webhooks</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="PullPayments">Pull payments</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="Payouts">Payouts</a>
|
||||
<a id="@(nameof(StoreNavPages.PullPayments))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pull payments</a>
|
||||
<a id="@(nameof(StoreNavPages.Payouts))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Payouts</a>
|
||||
<vc:ui-extension-point location="store-nav" model="@Model" />
|
||||
</nav>
|
||||
|
|
Loading…
Add table
Reference in a new issue