Move wallet payment settings back to store settings (#6251)

Intermediate solution, until we implement these settings on the payment method level. Closes #6237.
This commit is contained in:
d11n 2024-09-30 12:13:51 +02:00 committed by GitHub
parent 6d284b4124
commit 82620ee327
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 130 additions and 168 deletions

View file

@ -146,19 +146,27 @@ namespace BTCPayServer.Tests
public async Task ModifyPayment(Action<GeneralSettingsViewModel> modify)
{
var storeController = GetController<UIStoresController>();
var response = await storeController.GeneralSettings();
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model;
var response = await storeController.GeneralSettings(StoreId);
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model!;
modify(settings);
await storeController.GeneralSettings(settings);
}
public async Task ModifyGeneralSettings(Action<GeneralSettingsViewModel> modify)
{
var storeController = GetController<UIStoresController>();
var response = await storeController.GeneralSettings(StoreId);
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model!;
modify(settings);
storeController.GeneralSettings(settings).GetAwaiter().GetResult();
}
public async Task ModifyOnchainPaymentSettings(Action<WalletSettingsViewModel> modify)
{
var storeController = GetController<UIStoresController>();
var response = await storeController.WalletSettings(StoreId, "BTC");
WalletSettingsViewModel walletSettings = (WalletSettingsViewModel)((ViewResult)response).Model;
modify(walletSettings);
storeController.UpdatePaymentSettings(walletSettings).GetAwaiter().GetResult();
storeController.UpdateWalletSettings(walletSettings).GetAwaiter().GetResult();
}

View file

@ -303,7 +303,7 @@ namespace BTCPayServer.Tests
// Set tolerance to 50%
var stores = user.GetController<UIStoresController>();
var response = await stores.GeneralSettings();
var response = await stores.GeneralSettings(user.StoreId);
var vm = Assert.IsType<GeneralSettingsViewModel>(Assert.IsType<ViewResult>(response).Model);
Assert.Equal(0.0, vm.PaymentTolerance);
vm.PaymentTolerance = 50.0;
@ -385,7 +385,7 @@ namespace BTCPayServer.Tests
await user.RegisterDerivationSchemeAsync("BTC");
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
await user.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
await user.ModifyGeneralSettings(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
{
@ -445,7 +445,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount();
await user.GrantAccessAsync(true);
var storeController = user.GetController<UIStoresController>();
var storeResponse = await storeController.GeneralSettings();
var storeResponse = await storeController.GeneralSettings(user.StoreId);
Assert.IsType<ViewResult>(storeResponse);
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
@ -568,10 +568,10 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
await acc.GrantAccessAsync();
acc.RegisterDerivationScheme("BTC");
await acc.ModifyOnchainPaymentSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
var invoice = acc.BitPay.CreateInvoice(new Invoice
await acc.ModifyGeneralSettings(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
var invoice = await acc.BitPay.CreateInvoiceAsync(new Invoice
{
Price = 5.0m,
Currency = "USD",
@ -2619,7 +2619,7 @@ namespace BTCPayServer.Tests
}
var controller = tester.PayTester.GetController<UIStoresController>(user.UserId, user.StoreId);
var vm = await controller.GeneralSettings().AssertViewModelAsync<GeneralSettingsViewModel>();
var vm = await controller.GeneralSettings(user.StoreId).AssertViewModelAsync<GeneralSettingsViewModel>();
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/8f890691-87f9-4c65-80e5-3b7ffaa3551f-store.png", vm.LogoUrl);
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/2a51c49a-9d54-4013-80a2-3f6e69d08523-store.css", vm.CssUrl);

View file

@ -437,15 +437,10 @@ public partial class UIStoresController
}).ToList(),
Config = ProtectString(JToken.FromObject(derivation, handler.Serializer).ToString()),
PayJoinEnabled = storeBlob.PayJoinEnabled,
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
SpeedPolicy = store.SpeedPolicy,
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
CanUsePayJoin = canUseHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
CanUseHotWallet = canUseHotWallet,
CanUseRPCImport = rpcImport,
CanUsePayJoin = canUseHotWallet && network.SupportPayJoin && derivation.IsHotWallet,
StoreName = store.StoreName,
StoreName = store.StoreName
};
ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet);
@ -473,15 +468,14 @@ public partial class UIStoresController
var storeBlob = store.GetStoreBlob();
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
var currentlyEnabled = !excludeFilters.Match(handler.PaymentMethodId);
bool enabledChanged = currentlyEnabled != vm.Enabled;
bool needUpdate = enabledChanged;
var enabledChanged = currentlyEnabled != vm.Enabled;
var payjoinChanged = storeBlob.PayJoinEnabled != vm.PayJoinEnabled;
var needUpdate = enabledChanged || payjoinChanged;
string errorMessage = null;
if (enabledChanged)
{
storeBlob.SetExcluded(handler.PaymentMethodId, !vm.Enabled);
store.SetStoreBlob(storeBlob);
}
if (enabledChanged) storeBlob.SetExcluded(handler.PaymentMethodId, !vm.Enabled);
if (payjoinChanged && network.SupportPayJoin) storeBlob.PayJoinEnabled = vm.PayJoinEnabled;
if (needUpdate) store.SetStoreBlob(storeBlob);
if (derivation.Label != vm.Label)
{
@ -553,6 +547,15 @@ public partial class UIStoresController
successMessage += $" {vm.CryptoCode} on-chain payments are now {(vm.Enabled ? "enabled" : "disabled")} for this store.";
}
if (payjoinChanged && storeBlob.PayJoinEnabled && network.SupportPayJoin)
{
var config = store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), _handlers);
if (config?.IsHotWallet is not true)
{
successMessage += " However, PayJoin will not work, as this isn't a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>.";
}
}
TempData[WellKnownTempData.SuccessMessage] = successMessage;
}
else
@ -564,65 +567,6 @@ public partial class UIStoresController
return RedirectToAction(nameof(WalletSettings), new { vm.StoreId, vm.CryptoCode });
}
[HttpPost("{storeId}/onchain/{cryptoCode}/settings/payment")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> UpdatePaymentSettings(WalletSettingsViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
if (derivation == null)
{
return NotFound();
}
bool needUpdate = false;
var blob = store.GetStoreBlob();
var payjoinChanged = blob.PayJoinEnabled != vm.PayJoinEnabled;
blob.MonitoringExpiration = TimeSpan.FromMinutes(vm.MonitoringExpiration);
blob.ShowRecommendedFee = vm.ShowRecommendedFee;
blob.RecommendedFeeBlockTarget = vm.RecommendedFeeBlockTarget;
blob.PayJoinEnabled = vm.PayJoinEnabled;
if (store.SetStoreBlob(blob))
{
needUpdate = true;
}
if (store.SpeedPolicy != vm.SpeedPolicy)
{
store.SpeedPolicy = vm.SpeedPolicy;
needUpdate = true;
}
if (needUpdate)
{
await _storeRepo.UpdateStore(store);
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
if (payjoinChanged && blob.PayJoinEnabled && network.SupportPayJoin)
{
var config = store.GetPaymentMethodConfig<DerivationSchemeSettings>(PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode), _handlers);
if (config?.IsHotWallet is not true)
{
TempData.Remove(WellKnownTempData.SuccessMessage);
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "The payment settings were updated successfully. However, PayJoin will not work, as this isn't a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
});
}
}
}
return RedirectToAction(nameof(WalletSettings), new { vm.StoreId, vm.CryptoCode });
}
[HttpGet("{storeId}/onchain/{cryptoCode}/seed")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> WalletSeed(string storeId, string cryptoCode, CancellationToken cancellationToken = default)

View file

@ -20,11 +20,11 @@ namespace BTCPayServer.Controllers;
public partial class UIStoresController
{
[HttpGet("{storeId}/settings")]
public async Task<IActionResult> GeneralSettings()
public async Task<IActionResult> GeneralSettings(string storeId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
if (store == null) return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new GeneralSettingsViewModel
{
@ -41,7 +41,11 @@ public partial class UIStoresController
InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes,
DefaultCurrency = storeBlob.DefaultCurrency,
BOLT11Expiration = (long)storeBlob.RefundBOLT11Expiration.TotalDays,
Archived = store.Archived
Archived = store.Archived,
MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes,
SpeedPolicy = store.SpeedPolicy,
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget
};
return View(vm);
@ -67,13 +71,22 @@ public partial class UIStoresController
CurrentStore.StoreWebsite = model.StoreWebsite;
}
if (CurrentStore.SpeedPolicy != model.SpeedPolicy)
{
CurrentStore.SpeedPolicy = model.SpeedPolicy;
needUpdate = true;
}
var blob = CurrentStore.GetStoreBlob();
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
blob.NetworkFeeMode = model.NetworkFeeMode;
blob.PaymentTolerance = model.PaymentTolerance;
blob.DefaultCurrency = model.DefaultCurrency;
blob.ShowRecommendedFee = model.ShowRecommendedFee;
blob.RecommendedFeeBlockTarget = model.RecommendedFeeBlockTarget;
blob.InvoiceExpiration = TimeSpan.FromMinutes(model.InvoiceExpiration);
blob.RefundBOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration);
blob.MonitoringExpiration = TimeSpan.FromMinutes(model.MonitoringExpiration);
if (!string.IsNullOrEmpty(model.BrandColor) && !ColorPalette.IsValid(model.BrandColor))
{
ModelState.AddModelError(nameof(model.BrandColor), "The brand color needs to be a valid hex color code");

View file

@ -59,5 +59,19 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Minimum acceptable expiration time for BOLT11 for refunds")]
[Range(0, 365 * 10)]
public long BOLT11Expiration { 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 = "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 settled when the payment transaction …")]
public SpeedPolicy SpeedPolicy { get; set; }
}
}

View file

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using Newtonsoft.Json;
namespace BTCPayServer.Models.StoreViewModels
@ -16,20 +15,6 @@ namespace BTCPayServer.Models.StoreViewModels
[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 = "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 settled when the payment transaction …")]
public SpeedPolicy SpeedPolicy { get; set; }
public string Label { get; set; }
public string DerivationSchemeInput { get; set; }

View file

@ -122,13 +122,6 @@
<input asp-for="DefaultCurrency" class="form-control w-auto" currency-selection />
<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-3"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-check-label me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</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" title="More information...">
@ -173,6 +166,56 @@
</div>
<span asp-validation-for="BOLT11Expiration" 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" title="More information...">
<vc:icon symbol="info"/>
</a>
<div class="input-group">
<input inputmode="numeric" 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" title="More information...">
<vc:icon symbol="info"/>
</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>
<p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SpeedPolicy != 0)">
<vc:icon symbol="warning"/>
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
</p>
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div>
<div class="form-group d-flex align-items-center">
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-3"/>
<label asp-for="AnyoneCanCreateInvoice" class="form-check-label me-1"></label>
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</div>
<div class="d-flex align-items-center my-3">
<input asp-for="ShowRecommendedFee" type="checkbox" class="btcpay-toggle me-3" />
<div>
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
<div class="form-text">Fee will be shown for BTC and LTC onchain payments only.</div>
</div>
</div>
<div class="form-group">
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
<div class="input-group">
<input inputmode="numeric" asp-for="RecommendedFeeBlockTarget" class="form-control" min="1" style="max-width:10ch" />
<span class="input-group-text">blocks</span>
</div>
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
</div>
</div>
</div>
</form>

View file

@ -77,8 +77,21 @@
<input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-3"/>
<label asp-for="Enabled" class="form-check-label"></label>
</div>
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
<span asp-validation-for="Enabled" class="text-danger"></span>
</div>
@if (Model.CanUsePayJoin)
{
<div class="form-group mt-n2">
<div class="d-flex align-items-center">
<input asp-for="PayJoinEnabled" type="checkbox" class="btcpay-toggle me-3"/>
<label asp-for="PayJoinEnabled" class="form-check-label me-1"></label>
<a href="https://docs.btcpayserver.org/Payjoin/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</div>
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
</div>
}
<div class="form-group">
<label asp-for="Label" class="form-label"></label>
<input asp-for="Label" class="form-control" style="max-width:24em;" />
@ -143,64 +156,6 @@
<button type="submit" class="btn btn-primary mt-2" id="SaveWalletSettings">Save Wallet Settings</button>
</form>
<h3 class="mt-5 mb-4">Payment</h3>
<form method="post" asp-action="UpdatePaymentSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
@if (Model.CanUsePayJoin)
{
<div class="form-group">
<div class="d-flex align-items-center">
<input asp-for="PayJoinEnabled" type="checkbox" class="btcpay-toggle me-3"/>
<label asp-for="PayJoinEnabled" class="form-check-label me-1"></label>
<a href="https://docs.btcpayserver.org/Payjoin/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</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" title="More information...">
<vc:icon symbol="info"/>
</a>
<div class="input-group">
<input inputmode="numeric" 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" title="More information...">
<vc:icon symbol="info"/>
</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>
<p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SpeedPolicy != 0)">
<vc:icon symbol="warning"/>
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
</p>
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div>
<div class="d-flex align-items-center my-3">
<input asp-for="ShowRecommendedFee" type="checkbox" class="btcpay-toggle me-3" />
<div>
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
<div class="form-text">Fee will be shown for BTC and LTC onchain payments only.</div>
</div>
</div>
<div class="form-group my-3">
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
<input inputmode="numeric" asp-for="RecommendedFeeBlockTarget" class="form-control" min="1" style="width:8ch" />
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary mt-2" id="SavePaymentSettings">Save Payment Settings</button>
</form>
<h3 class="mt-5">Labels</h3>
<p>
<a asp-controller="UIWallets" asp-action="WalletLabels" asp-route-walletId="@Model.WalletId">Manage labels</a>