Unify GetStoreDataOrNull/GetInvoiceDataOrNull
What changed, and why it matters
This commit is a large internal cleanup that renames and unifies how BTCPay Server looks up store, invoice, app, and payment-request data from the HTTP request context. The main visible change is that many controllers stop manually checking the database for a store/invoice and instead rely on data that an authorization filter already placed in the request context. The commit also adds route-to-store mappings for paymentRequestId and pullPaymentId, and adds a new test that verifies a user with view-only invoice permission cannot read an invoice belonging to a different store. While the change is mostly defensive, it is also risky: if the authorization filter ever misses a route or a controller action is reachable without the filter, the controller may assume it has a valid store/invoice when it does not, which could lead to null-reference crashes or, in the worst case, cross-store data access.
Treat this as a security-sensitive refactoring. Review that every affected controller/action is still covered by the authorization pipeline that populates BTCPAY.STOREDATA/BTCPAY.INVOICEDATA/BTCPAY.APPDATA/BTCPAY.PAYMENTREQUESTDATA. Pay special attention to actions that removed explicit null checks and now call GetStoreData() (throwing) or GetStoreDataOrNull() without fallback. Run the new GreenfieldAPITests regression and add similar cross-store/cross-resource negative tests for payment requests, pull payments, apps, and webhooks. Consider adding controller-specific filters for plugin routes rather than relying solely on the global SetContextFilter, as the commit comments suggest.
Security signals we found
Authorization-context lookup consolidation across Greenfield and UI controllers
Removal of explicit per-controller store/invoice existence checks in favor of filter-populated context
New route-value-to-store-id mappings for paymentRequestId and pullPaymentId
Added regression test for cross-store invoice access via Greenfield API
Change from throwing helpers to nullable helpers in many UI controllers
Potential for null-store dereference if SetContextFilter does not run or misses a route
Evidence from the diff
The patch renames HttpContext extension methods: GetStoreData -> GetStoreDataOrNull, GetStoreDataOrThrow -> GetStoreData, GetInvoiceData -> GetInvoiceDataOrNull, GetPaymentRequestData -> GetPaymentRequestDataOrNull, GetAppData -> GetAppDataOrNull. It then updates ~50 call sites to use the new names and removes many explicit store/invoice null checks in Greenfield controllers, relying on SetContextFilter/BuiltInPermissionScopeProvider to populate BTCPAY.* context items after authorization. New route-value-to-store-id queries are registered for paymentRequestId and pullPaymentId. A GreenfieldAPITests change adds assertions that GetInvoice(‘some-other-store’, id) returns missing-permission and GetInvoice(otherStoreId, id) returns invoice-not-found. Several UI controllers switch from throwing helpers to nullable helpers and add inline null checks. There are also incidental fixes in Bitpay and LNURL controllers (null checks, removed unused serializer, lightning prompt null handling).
Changed components
BTCPayServer/Extensions.csBTCPayServer/Security/SetContextFilter.csBTCPayServer/Security/BuiltInPermissionScopeProvider.csBTCPayServer/Hosting/BTCPayServerServices.csGreenfield invoice/payment-request/pull-payment/webhook/store controllersUI store/app/invoice/wallet/LNURL/payment-request controllersBitpay plugin invoice/rate/token controllersGreenfieldAPITests.csInspect captured patch +196 / −273
diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs
index b24a5b3..9357d9a 100644
--- a/BTCPayServer.Tests/GreenfieldAPITests.cs
+++ b/BTCPayServer.Tests/GreenfieldAPITests.cs
@@ -1999,8 +1999,13 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
- await user.GrantAccessAsync(true);
- await user.MakeAdmin();
+
+ await user.RegisterAsync(true);
+ await user.CreateStoreAsync();
+ var otherStoreId = user.StoreId;
+ await user.CreateStoreAsync();
+ await user.PairWithBitpayAPI();
+
await user.SetupWebhook();
var client = await user.CreateClient(Policies.Unrestricted);
var viewOnly = await user.CreateClient(Policies.CanViewInvoices);
@@ -2116,6 +2121,9 @@ namespace BTCPayServer.Tests
//get
var invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
+
+ await AssertEx.AssertApiError("missing-permission", async () => await viewOnly.GetInvoice("some-other-store", newInvoice.Id));
+ await AssertEx.AssertApiError("invoice-not-found", async () => await viewOnly.GetInvoice(otherStoreId, newInvoice.Id));
Assert.True(JObject.DeepEquals(newInvoice.Metadata, invoice.Metadata));
var paymentMethods = await viewOnly.GetInvoicePaymentMethods(user.StoreId, newInvoice.Id);
Assert.Single(paymentMethods);
diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs
index 42c8db2..dc9d1ce 100644
--- a/BTCPayServer.Tests/TestAccount.cs
+++ b/BTCPayServer.Tests/TestAccount.cs
@@ -94,10 +94,16 @@ namespace BTCPayServer.Tests
{
await RegisterAsync(isAdmin);
await CreateStoreAsync();
+ await PairWithBitpayAPI();
+ }
+
+ public async Task PairWithBitpayAPI()
+ {
var store = GetController<BTCPayServer.Plugins.Bitpay.Controllers.UIStoresTokenController>();
- var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
+ var pairingCode = await BitPay.RequestClientAuthorizationAsync("test", Facade.Merchant);
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
- await store.Pair(pairingCode.ToString(), StoreId);
+ var result = await store.Pair(pairingCode.ToString(), StoreId);
+ Assert.IsType<RedirectToActionResult>(result);
}
public BTCPayServerClient CreateClientFromAPIKey(string apiKey)
diff --git a/BTCPayServer/Components/AppSales/AppSales.cs b/BTCPayServer/Components/AppSales/AppSales.cs
index 02670e1..4caf206 100644
--- a/BTCPayServer/Components/AppSales/AppSales.cs
+++ b/BTCPayServer/Components/AppSales/AppSales.cs
@@ -31,12 +31,12 @@ public class AppSales : ViewComponent
Id = appId,
AppType = appType,
DataUrl = Url.Action("AppSales", "UIApps", new { appId }),
- InitialRendering = HttpContext.GetAppData()?.Id != appId
+ InitialRendering = HttpContext.GetAppDataOrNull()?.Id != appId
};
if (vm.InitialRendering)
return View(vm);
- var app = HttpContext.GetAppData();
+ var app = HttpContext.GetAppDataOrNull();
var stats = await _appService.GetSalesStats(app);
vm.SalesCount = stats.SalesCount;
vm.Series = stats.Series;
diff --git a/BTCPayServer/Components/AppTopItems/AppTopItems.cs b/BTCPayServer/Components/AppTopItems/AppTopItems.cs
index 6c17b06..717962a 100644
--- a/BTCPayServer/Components/AppTopItems/AppTopItems.cs
+++ b/BTCPayServer/Components/AppTopItems/AppTopItems.cs
@@ -27,12 +27,12 @@ public class AppTopItems : ViewComponent
Id = appId,
AppType = appType,
DataUrl = Url.Action("AppTopItems", "UIApps", new { appId }),
- InitialRendering = HttpContext.GetAppData()?.Id != appId
+ InitialRendering = HttpContext.GetAppDataOrNull()?.Id != appId
};
if (vm.InitialRendering)
return View(vm);
- var app = HttpContext.GetAppData();
+ var app = HttpContext.GetAppDataOrNull();
var entries = await _appService.GetItemStats(app);
vm.SalesCount = entries.Select(e => e.SalesCount).ToList();
vm.Entries = entries.Take(5).ToList();
diff --git a/BTCPayServer/Components/WalletNav/WalletNav.cs b/BTCPayServer/Components/WalletNav/WalletNav.cs
index acce5bc..5536c70 100644
--- a/BTCPayServer/Components/WalletNav/WalletNav.cs
+++ b/BTCPayServer/Components/WalletNav/WalletNav.cs
@@ -43,7 +43,7 @@ namespace BTCPayServer.Components.WalletNav
public async Task<IViewComponentResult> InvokeAsync(WalletId walletId)
{
- var store = ViewContext.HttpContext.GetStoreData();
+ var store = ViewContext.HttpContext.GetStoreDataOrNull();
var network = _handlers.TryGetNetwork(PaymentTypes.CHAIN.GetPaymentMethodId(walletId.CryptoCode));
if (network is null || store is null)
return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs
index 4a05f46..b12f14f 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldAppsController.cs
@@ -59,10 +59,7 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreateCrowdfundApp(string storeId, CrowdfundAppRequest request)
{
- var store = await _storeRepository.FindStore(storeId);
- if (store == null)
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
-
+ var store = HttpContext.GetStoreData();
// This is not obvious, but we must have a non-null currency or else request validation may not work correctly
request.TargetCurrency ??= store.GetStoreBlob().DefaultCurrency;
@@ -93,10 +90,7 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CreatePointOfSaleApp(string storeId, PointOfSaleAppRequest request)
{
- var store = await _storeRepository.FindStore(storeId);
- if (store == null)
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
-
+ var store = HttpContext.GetStoreData();
// This is not obvious, but we must have a non-null currency or else request validation may not work correctly
request.Currency ??= store.GetStoreBlob().DefaultCurrency;
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs
index 4fc4044..1468df6 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldInvoiceController.cs
@@ -97,7 +97,7 @@ namespace BTCPayServer.Controllers.Greenfield
[FromQuery] int? take = null
)
{
- var store = HttpContext.GetStoreData()!;
+ var store = HttpContext.GetStoreData();
if (startDate is DateTimeOffset s &&
endDate is DateTimeOffset e &&
s > e)
@@ -130,7 +130,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
public async Task<IActionResult> GetInvoice(string storeId, string invoiceId)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
return Ok(ToModel(invoice));
@@ -141,7 +141,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpDelete("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
public async Task<IActionResult> ArchiveInvoice(string storeId, string invoiceId)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
await _invoiceRepository.ToggleInvoiceArchival(invoiceId, true, storeId);
@@ -153,7 +153,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPut("~/api/v1/stores/{storeId}/invoices/{invoiceId}")]
public async Task<IActionResult> UpdateInvoice(string storeId, string invoiceId, UpdateInvoiceRequest request)
{
- if (HttpContext.GetInvoiceData() is null)
+ if (HttpContext.GetInvoiceDataOrNull() is null)
return InvoiceNotFound();
var invoice = await _invoiceRepository.UpdateInvoiceMetadata(invoiceId, storeId, request.Metadata);
return Ok(ToModel(invoice));
@@ -164,7 +164,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPost("~/api/v1/stores/{storeId}/invoices")]
public async Task<IActionResult> CreateInvoice(string storeId, CreateInvoiceRequest request)
{
- var store = HttpContext.GetStoreData()!;
+ var store = HttpContext.GetStoreData();
if (request.Amount < 0.0m)
{
ModelState.AddModelError(nameof(request.Amount), "The amount should be 0 or more.");
@@ -238,7 +238,7 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> MarkInvoiceStatus(string storeId, string invoiceId,
MarkInvoiceStatusRequest request)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
@@ -259,7 +259,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/unarchive")]
public async Task<IActionResult> UnarchiveInvoice(string storeId, string invoiceId)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
@@ -281,7 +281,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods")]
public async Task<IActionResult> GetInvoicePaymentMethods(string storeId, string invoiceId, bool onlyAccountedPayments = true, bool includeSensitive = false)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
@@ -296,7 +296,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate")]
public async Task<IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod)
{
- if (HttpContext.GetInvoiceData() is null)
+ if (HttpContext.GetInvoiceDataOrNull() is null)
return InvoiceNotFound();
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
{
@@ -317,9 +317,9 @@ namespace BTCPayServer.Controllers.Greenfield
CancellationToken cancellationToken = default
)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
var store = HttpContext.GetStoreData();
- if (invoice is null || store is null)
+ if (invoice is null)
return InvoiceNotFound();
if (!invoice.GetInvoiceState().CanRefund())
return this.CreateAPIError("non-refundable", "Cannot refund this invoice");
@@ -488,7 +488,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/invoices/{invoiceId}/refund/{paymentMethodId}")]
public async Task<IActionResult> GetInvoiceRefundTriggerData(string storeId, string invoiceId, string paymentMethodId, CancellationToken cancellationToken)
{
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice is null)
return InvoiceNotFound();
var pmi = PaymentMethodId.TryParse(paymentMethodId);
@@ -517,7 +517,7 @@ namespace BTCPayServer.Controllers.Greenfield
var cdCurrency = _currencyNameTable.GetCurrencyData(invoice.Currency, true);
var paidAmount = Math.Round(cryptoPaid * paymentPrompt.Rate, cdCurrency.Divisibility);
- var store = this.HttpContext.GetStoreDataOrThrow();
+ var store = this.HttpContext.GetStoreData();
var rules = store.GetStoreBlob().GetRateRules(_defaultRules);
var rateResult = await _rateProvider.FetchRate(
new CurrencyPair(paymentMethodCurrency, invoice.Currency), rules, new StoreIdRateContext(store.Id),
@@ -650,7 +650,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
statuses.Add(InvoiceStatus.Invalid);
}
- var store = request?.HttpContext.GetStoreData();
+ var store = request?.HttpContext.GetStoreDataOrNull();
var receipt = store == null ? entity.ReceiptOptions : InvoiceDataBase.ReceiptOptions.Merge(store.GetStoreBlob().ReceiptOptions, entity.ReceiptOptions);
return new InvoiceData
{
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs
index f3ad4d0..eb029ba 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs
@@ -55,7 +55,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
return base.GetBalance(cryptoCode, cancellationToken);
}
-
+
[Authorize(Policy = Policies.CanUseLightningNodeInStore, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/histogram")]
public override Task<IActionResult> GetHistogram(string cryptoCode, [FromQuery] HistogramType? type = null, CancellationToken cancellationToken = default)
@@ -153,11 +153,6 @@ namespace BTCPayServer.Controllers.Greenfield
}
var network = handler.Network;
var store = HttpContext.GetStoreData();
- if (store == null)
- {
- throw new JsonHttpException(StoreNotFound());
- }
-
var id = PaymentTypes.LN.GetPaymentMethodId(cryptoCode);
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id, _handlers);
if (existing == null)
@@ -178,10 +173,5 @@ namespace BTCPayServer.Controllers.Greenfield
}
throw ErrorLightningNodeNotConfiguredForStore();
}
-
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
}
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs
index 6cf6973..c4d38e6 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs
@@ -59,26 +59,23 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-requests/{paymentRequestId}")]
public async Task<IActionResult> GetPaymentRequest(string storeId, string paymentRequestId)
- {
- var pr = await _paymentRequestRepository.FindPaymentRequests(
- new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId } });
+ {
+ var pr = HttpContext.GetPaymentRequestDataOrNull();
- if (pr.Length == 0)
- {
+ if (pr is null)
return PaymentRequestNotFound();
- }
- return Ok(FromModel(pr.First()));
+ return Ok(FromModel(pr));
}
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPost("~/api/v1/stores/{storeId}/payment-requests/{paymentRequestId}/pay")]
public async Task<IActionResult> PayPaymentRequest(string storeId, string paymentRequestId, [FromBody] PayPaymentRequestRequest pay, CancellationToken cancellationToken)
{
- var pr = await this.PaymentRequestService.GetPaymentRequest(paymentRequestId);
- if (pr is null || pr.StoreId != storeId)
+ var p = HttpContext.GetPaymentRequestDataOrNull();
+ if (p is null)
return PaymentRequestNotFound();
-
+ var pr = await PaymentRequestService.AsViewModel(p);
var amount = pay?.Amount;
if (amount.HasValue && amount.Value <= 0)
{
@@ -118,8 +115,9 @@ namespace BTCPayServer.Controllers.Greenfield
try
{
+ var storeData = HttpContext.GetStoreData();
var prData = await _paymentRequestRepository.FindPaymentRequest(pr.Id, null);
- var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, this.StoreData, Request, cancellationToken);
+ var invoice = await _invoiceController.CreatePaymentRequestInvoice(prData, amount, pr.AmountDue, storeData, Request, cancellationToken);
return Ok(GreenfieldInvoiceController.ToModel(invoice, _linkGenerator, _currencyNameTable, Request));
}
catch (BitpayHttpException e)
@@ -133,14 +131,11 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpDelete("~/api/v1/stores/{storeId}/payment-requests/{paymentRequestId}")]
public async Task<IActionResult> ArchivePaymentRequest(string storeId, string paymentRequestId)
{
- var pr = await _paymentRequestRepository.FindPaymentRequests(
- new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId }, IncludeArchived = false });
- if (pr.Length == 0)
- {
- return PaymentRequestNotFound();
- }
+ var pr = HttpContext.GetPaymentRequestDataOrNull();
+ if (pr is null || pr.Archived)
+ return PaymentRequestNotFound();
- await _paymentRequestRepository.ArchivePaymentRequest(pr.First().Id);
+ await _paymentRequestRepository.ArchivePaymentRequest(pr.Id);
return Ok();
}
@@ -168,11 +163,11 @@ namespace BTCPayServer.Controllers.Greenfield
if (string.IsNullOrEmpty(request.Title))
ModelState.AddModelError(nameof(request.Title), "Title is required");
+ var storeData = HttpContext.GetStoreData();
PaymentRequestData pr;
if (paymentRequestId is not null)
{
- pr = (await _paymentRequestRepository.FindPaymentRequests(
- new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId } })).FirstOrDefault();
+ pr = HttpContext.GetPaymentRequestDataOrNull();
if (pr is null)
return PaymentRequestNotFound();
if ((pr.Amount != request.Amount && request.Amount != 0.0m) ||
@@ -201,7 +196,7 @@ namespace BTCPayServer.Controllers.Greenfield
Status = PaymentRequestStatus.Pending,
Created = DateTimeOffset.UtcNow,
Amount = request.Amount,
- Currency = request.Currency ?? StoreData.GetStoreBlob().DefaultCurrency,
+ Currency = request.Currency ?? storeData.GetStoreBlob().DefaultCurrency,
Expiry = request.ExpiryDate,
};
}
@@ -225,7 +220,6 @@ namespace BTCPayServer.Controllers.Greenfield
pr = await _paymentRequestRepository.CreateOrUpdatePaymentRequest(pr);
return Ok(FromModel(pr));
}
- public Data.StoreData StoreData => HttpContext.GetStoreData();
public PaymentRequestService PaymentRequestService { get; }
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
index 799aa41..532b42a 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldPullPaymentController.cs
@@ -125,7 +125,8 @@ namespace BTCPayServer.Controllers.Greenfield
ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive");
}
- var supported = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
+ var storeData = HttpContext.GetStoreData();
+ var supported = _payoutHandlers.GetSupportedPayoutMethods(storeData);
if (request.PayoutMethods is not null)
{
for (int i = 0; i < request.PayoutMethods.Length; i++)
@@ -144,7 +145,7 @@ namespace BTCPayServer.Controllers.Greenfield
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
- var ppId = await _pullPaymentService.CreatePullPayment(HttpContext.GetStoreData(), request);
+ var ppId = await _pullPaymentService.CreatePullPayment(storeData, request);
var pp = await _pullPaymentService.GetPullPayment(ppId, false);
return this.Ok(CreatePullPaymentData(pp));
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldServerRolesController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldServerRolesController.cs
index 7448b76..73ba307 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldServerRolesController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldServerRolesController.cs
@@ -34,9 +34,4 @@ public class GreenfieldServerRolesController : ControllerBase
{
return data.Select(r => new RoleData() {Role = r.Role, Id = r.Id, Permissions = r.Permissions, IsServerRole = true}).ToList();
}
-
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
index 41e8143..1cb0fd8 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
@@ -33,20 +33,12 @@ namespace BTCPayServer.Controllers.GreenField
[FromBody] SendEmailRequest request)
{
var store = HttpContext.GetStoreData();
- if (store == null)
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
if (!MailboxAddressValidator.TryParse(request.Email, out var to))
{
ModelState.AddModelError(nameof(request.Email), "Invalid email");
return this.CreateValidationError(ModelState);
}
var emailSender = await _emailSenderFactory.GetEmailSender(storeId);
- if (emailSender is null)
- {
- return this.CreateAPIError(404, "smtp-not-configured", "Store does not have an SMTP server configured.");
- }
emailSender.SendEmail(to, request.Subject, request.Body);
return Ok();
}
@@ -61,10 +53,7 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/email")]
public IActionResult GetStoreEmailSettings()
- {
- var store = HttpContext.GetStoreData();
- return store == null ? StoreNotFound() : Ok(ToApiModel(store));
- }
+ => Ok(ToApiModel(HttpContext.GetStoreData()));
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/stores/{storeId}/email")]
@@ -76,7 +65,7 @@ namespace BTCPayServer.Controllers.GreenField
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
- var store = HttpContext.GetStoreDataOrThrow();
+ var store = HttpContext.GetStoreData();
var blob = store.GetStoreBlob();
var settings = EmailSettings.FromData(request, blob.EmailSettings?.Password);
blob.EmailSettings = settings;
@@ -85,10 +74,5 @@ namespace BTCPayServer.Controllers.GreenField
return Ok(ToApiModel(store));
}
-
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
}
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs
index c7c3bff..b0bdada 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs
@@ -38,7 +38,7 @@ namespace BTCPayServer.Controllers.Greenfield
[EnableCors(CorsPolicies.All)]
public class GreenfieldStoreOnChainWalletsController : ControllerBase
{
- private StoreData Store => HttpContext.GetStoreDataOrThrow();
+ private StoreData Store => HttpContext.GetStoreData();
public PoliciesSettings PoliciesSettings { get; }
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStorePaymentMethodsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStorePaymentMethodsController.cs
index dd6d629..3dcf30c 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStorePaymentMethodsController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStorePaymentMethodsController.cs
@@ -28,7 +28,7 @@ namespace BTCPayServer.Controllers.Greenfield
IAuthorizationService authorizationService)
: ControllerBase
{
- private StoreData Store => HttpContext.GetStoreDataOrThrow();
+ private StoreData Store => HttpContext.GetStoreData();
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}")]
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs
index 9c06894..d045a37 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesConfigurationController.cs
@@ -53,7 +53,7 @@ namespace BTCPayServer.Controllers.GreenField
[NonAction]
private IActionResult GetStoreRateConfigurationCore(bool? fallback)
{
- var data = HttpContext.GetStoreDataOrThrow();
+ var data = HttpContext.GetStoreData();
var storeBlob = data.GetStoreBlob();
var blob = storeBlob.GetRateSettings(fallback ?? false);
if (blob is null)
@@ -92,7 +92,7 @@ namespace BTCPayServer.Controllers.GreenField
[NonAction]
private async Task<IActionResult> UpdateStoreRateConfigurationCore(StoreRateConfiguration configuration, bool fallback)
{
- var storeData = HttpContext.GetStoreDataOrThrow();
+ var storeData = HttpContext.GetStoreData();
var storeBlob = storeData.GetStoreBlob();
var blob = storeBlob.GetRateSettings(fallback);
@@ -124,7 +124,7 @@ namespace BTCPayServer.Controllers.GreenField
public async Task<IActionResult> PreviewUpdateStoreRateConfiguration(
StoreRateConfiguration configuration, [FromQuery] string[]? currencyPair)
{
- var data = HttpContext.GetStoreDataOrThrow();
+ var data = HttpContext.GetStoreData();
var storeBlob = data.GetStoreBlob();
// Fallback or not, the preview will be the same
var blob = storeBlob.GetOrCreateRateSettings(true);
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs
index 923b8d7..639ce87 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRatesController.cs
@@ -36,7 +36,7 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> GetStoreRates([FromQuery] string[]? currencyPair)
{
- var data = HttpContext.GetStoreDataOrThrow();
+ var data = HttpContext.GetStoreData();
var blob = data.GetStoreBlob();
var parsedCurrencyPairs = new HashSet<CurrencyPair>();
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRolesController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRolesController.cs
index 14621f5..80e7ad9 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreRolesController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreRolesController.cs
@@ -15,33 +15,16 @@ namespace BTCPayServer.Controllers.Greenfield
[ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[EnableCors(CorsPolicies.All)]
- public class GreenfieldStoreRolesController : ControllerBase
+ public class GreenfieldStoreRolesController(StoreRepository storeRepository) : ControllerBase
{
- private readonly StoreRepository _storeRepository;
-
- public GreenfieldStoreRolesController(StoreRepository storeRepository)
- {
- _storeRepository = storeRepository;
- }
-
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/roles")]
public async Task<IActionResult> GetStoreRoles(string storeId)
- {
- var store = HttpContext.GetStoreData();
- return store == null
- ? StoreNotFound()
- : Ok(FromModel(await _storeRepository.GetStoreRoles(storeId, false)));
- }
+ => Ok(FromModel(await storeRepository.GetStoreRoles(storeId, false)));
private List<RoleData> FromModel(StoreRepository.StoreRole[] data)
{
return data.Select(r => new RoleData() {Role = r.Role, Id = r.Id, Permissions = r.Permissions, IsServerRole = r.IsServerRole}).ToList();
}
-
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
}
}
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreUsersController.cs
index 9588936..4b57f2a 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreUsersController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreUsersController.cs
@@ -42,19 +42,12 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/users")]
public async Task<IActionResult> GetStoreUsers()
- {
- var store = HttpContext.GetStoreData();
- return store == null ? StoreNotFound() : Ok(await ToAPI(store));
- }
+ => Ok(await ToAPI(HttpContext.GetStoreData()));
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}/users/{idOrEmail}")]
public async Task<IActionResult> RemoveStoreUser(string storeId, string idOrEmail)
{
- var store = HttpContext.GetStoreData();
- if (store == null)
- return StoreNotFound();
-
var user = await _userManager.FindByIdOrEmail(idOrEmail);
if (user == null)
return UserNotFound();
@@ -69,10 +62,6 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpPut("~/api/v1/stores/{storeId}/users/{idOrEmail?}")]
public async Task<IActionResult> AddOrUpdateStoreUser(string storeId, StoreUserData request, string idOrEmail = null)
{
- var store = HttpContext.GetStoreData();
- if (store == null)
- return StoreNotFound();
-
// Deprecated properties
request.StoreRole ??= request.AdditionalData.TryGetValue("role", out var role) ? role.ToString() : null;
request.Id ??= request.AdditionalData.TryGetValue("userId", out var userId) ? userId.ToString() : null;
@@ -133,11 +122,6 @@ namespace BTCPayServer.Controllers.Greenfield
return storeUsers;
}
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
-
private IActionResult UserNotFound()
{
return this.CreateAPIError(404, "user-not-found", "The user was not found");
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs
index 88982f0..aa17e97 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoresController.cs
@@ -70,18 +70,12 @@ namespace BTCPayServer.Controllers.Greenfield
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}")]
public async Task<IActionResult> GetStore(string storeId)
- {
- var store = HttpContext.GetStoreData();
- return store == null ? StoreNotFound() : Ok(await FromModel(store));
- }
+ => Ok(await FromModel(HttpContext.GetStoreData()));
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}")]
public async Task<IActionResult> RemoveStore(string storeId)
{
- var store = HttpContext.GetStoreData();
- if (store == null) return StoreNotFound();
-
await _storeRepository.RemoveStore(storeId, User.GetId());
return Ok();
}
@@ -118,7 +112,6 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> UpdateStore(string storeId, UpdateStoreRequest request)
{
var store = HttpContext.GetStoreData();
- if (store == null) return StoreNotFound();
request = await MergeStoreRequestWithTemplate(request, store);
var validationResult = Validate(request);
if (validationResult != null) return validationResult;
@@ -133,7 +126,8 @@ namespace BTCPayServer.Controllers.Greenfield
{
var user = await _userManager.GetUserAsync(User);
var store = HttpContext.GetStoreData();
- if (user == null || store == null) return StoreNotFound();
+ if (user == null)
+ return this.CreateAPIError(400, "user-not-found", "The user was not found");
UploadImageResultModel upload = null;
if (file is null)
@@ -168,13 +162,12 @@ namespace BTCPayServer.Controllers.Greenfield
public async Task<IActionResult> DeleteStoreLogo(string storeId)
{
var store = HttpContext.GetStoreData();
- if (store == null || User.GetIdOrNull() is not string userId) return StoreNotFound();
var blob = store.GetStoreBlob();
var fileId = (blob.LogoUrl as UnresolvedUri.FileIdUri)?.FileId;
if (!string.IsNullOrEmpty(fileId))
{
- await _fileService.RemoveFile(fileId, userId);
+ await _fileService.RemoveFile(fileId, User.GetId());
blob.LogoUrl = null;
store.SetStoreBlob(blob);
await _storeRepository.UpdateStore(store);
@@ -381,10 +374,5 @@ namespace BTCPayServer.Controllers.Greenfield
}
return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null;
}
-
- private IActionResult StoreNotFound()
- {
- return this.CreateAPIError(404, "store-not-found", "The store was not found");
- }
}
}
diff --git a/BTCPayServer/Controllers/UIAppsController.Dashboard.cs b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs
index 8bb70b7..a139f64 100644
--- a/BTCPayServer/Controllers/UIAppsController.Dashboard.cs
+++ b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs
@@ -14,11 +14,11 @@ namespace BTCPayServer.Controllers
[HttpGet("{appId}/dashboard/app-top-items")]
public IActionResult AppTopItems(string appId)
{
- var app = HttpContext.GetAppData();
+ var app = HttpContext.GetAppDataOrNull();
if (app == null)
return NotFound();
- app.StoreData = GetCurrentStore();
+ app.StoreData = HttpContext.GetStoreData();
return ViewComponent("AppTopItems", new { appId = app.Id, appType = app.AppType });
}
@@ -27,11 +27,11 @@ namespace BTCPayServer.Controllers
[HttpGet("{appId}/dashboard/app-sales")]
public IActionResult AppSales(string appId)
{
- var app = HttpContext.GetAppData();
+ var app = HttpContext.GetAppDataOrNull();
if (app == null)
return NotFound();
- app.StoreData = GetCurrentStore();
+ app.StoreData = HttpContext.GetStoreData();
return ViewComponent("AppSales", new { appId = app.Id, appType = app.AppType });
}
@@ -39,11 +39,11 @@ namespace BTCPayServer.Controllers
[HttpGet("{appId}/dashboard/app-sales/{period}")]
public async Task<IActionResult> AppSales(string appId, AppSalesPeriod period)
{
- var app = HttpContext.GetAppData();
+ var app = HttpContext.GetAppDataOrNull();
if (app == null)
return NotFound();
- app.StoreData = GetCurrentStore();
+ app.StoreData = HttpContext.GetStoreData();
var days = period switch
{
@@ -52,10 +52,7 @@ namespace BTCPayServer.Controllers
_ => throw new ArgumentException($"AppSalesPeriod {period} does not exist.")
};
var stats = await _appService.GetSalesStats(app, days);
-
- return stats == null
- ? NotFound()
- : Json(stats);
+ return Json(stats);
}
}
}
diff --git a/BTCPayServer/Controllers/UIAppsController.cs b/BTCPayServer/Controllers/UIAppsController.cs
index 01b6316..147a13b 100644
--- a/BTCPayServer/Controllers/UIAppsController.cs
+++ b/BTCPayServer/Controllers/UIAppsController.cs
@@ -86,7 +86,7 @@ namespace BTCPayServer.Controllers
bool archived = false
)
{
- var store = GetCurrentStore();
+ var store = HttpContext.GetStoreData();
var apps = (await _appService.GetAllApps(GetUserId(), false, store.Id, archived))
.Where(app => app.Archived == archived);
@@ -138,11 +138,7 @@ namespace BTCPayServer.Controllers
[HttpPost("/stores/{storeId}/apps/create/{appType?}")]
public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm)
{
- var store = GetCurrentStore();
- if (store == null)
- {
- return NotFound();
- }
+ var store = HttpContext.GetStoreData();
if (!store.AnyPaymentMethodAvailable(_handlers))
{
object text = _networkProvider.DefaultNetwork?.CryptoCode switch
@@ -298,8 +294,6 @@ namespace BTCPayServer.Controllers
private string GetUserId() => User.GetId();
- private StoreData GetCurrentStore() => HttpContext.GetStoreData();
-
- private AppData GetCurrentApp() => HttpContext.GetAppData();
+ private AppData GetCurrentApp() => HttpContext.GetAppDataOrNull();
}
}
diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs
index a0c074b..0c56a8b 100644
--- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs
+++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs
@@ -296,7 +296,7 @@ namespace BTCPayServer.Controllers
new { pullPaymentId = ppId });
}
- var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(this.GetCurrentStore());
+ var payoutMethodIds = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
if (!payoutMethodIds.Any())
{
var vm = new RefundModel { Title = StringLocalizer["No matching payment method"] };
@@ -332,12 +332,12 @@ namespace BTCPayServer.Controllers
{
await using var ctx = _dbContextFactory.CreateContext();
- var invoice = HttpContext.GetInvoiceData();
+ var invoice = HttpContext.GetInvoiceDataOrNull();
if (invoice?.GetInvoiceState().CanRefund() is not true)
return NotFound();
- var store = GetCurrentStore();
+ var store = HttpContext.GetStoreData();
var pmi = PayoutMethodId.Parse(model.SelectedPayoutMethod);
var cdCurrency = _CurrencyNameTable.GetCurrencyData(invoice.Currency, true);
RateRulesCollection rules;
@@ -627,6 +627,8 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.ErrorMessage] = err;
return RedirectToAction(nameof(ListInvoices), new { storeId });
}
+
+ var store = HttpContext.GetStoreData();
if (selectedItems.Length == 0)
return NotSupported(StringLocalizer["No invoice has been selected"]);
@@ -650,10 +652,10 @@ namespace BTCPayServer.Controllers
var explorer = network is null ? null : _ExplorerClients.GetExplorerClient(network);
if (explorer is null || network is null)
return NotSupported(StringLocalizer["This feature is only available to BTC wallets"]);
- if (!GetCurrentStore().HasPolicy(User.GetId(), Policies.CanModifyStoreSettings, _permissionService))
+ if (!store.HasPolicy(User.GetId(), Policies.CanModifyStoreSettings, _permissionService))
return Forbid();
- var derivationScheme = GetCurrentStore().GetDerivationSchemeSettings(_handlers, network.CryptoCode)?.AccountDerivation;
+ var derivationScheme = store.GetDerivationSchemeSettings(_handlers, network.CryptoCode)?.AccountDerivation;
if (derivationScheme is null)
return NotSupported("This feature is only available to BTC wallets");
var btc = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
@@ -1174,7 +1176,7 @@ namespace BTCPayServer.Controllers
[Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken)
{
- var store = HttpContext.GetStoreDataOrThrow();
+ var store = HttpContext.GetStoreData();
if (!store.AnyPaymentMethodAvailable(_handlers))
{
return NoPaymentMethodResult(store.Id);
@@ -1292,8 +1294,6 @@ namespace BTCPayServer.Controllers
public string? StatusString { get; set; }
}
- private StoreData GetCurrentStore() => HttpContext.GetStoreDataOrThrow();
-
// Let server admin lookup invoices from users, see #6489
private string? GetUserIdForInvoiceQuery() => User.IsInRole(Roles.ServerAdmin) ? null : User.GetIdOrNull();
diff --git a/BTCPayServer/Controllers/UILNURLController.cs b/BTCPayServer/Controllers/UILNURLController.cs
index 7721d61..85a48d1 100644
--- a/BTCPayServer/Controllers/UILNURLController.cs
+++ b/BTCPayServer/Controllers/UILNURLController.cs
@@ -58,7 +58,6 @@ namespace BTCPayServer
private readonly LinkGenerator _linkGenerator;
private readonly LightningAddressService _lightningAddressService;
private readonly PullPaymentHostedService _pullPaymentHostedService;
- private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly IPluginHookService _pluginHookService;
private readonly InvoiceActivator _invoiceActivator;
private readonly PaymentMethodHandlerDictionary _handlers;
@@ -78,7 +77,6 @@ namespace BTCPayServer
LinkGenerator linkGenerator,
LightningAddressService lightningAddressService,
PullPaymentHostedService pullPaymentHostedService,
- BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
IPluginHookService pluginHookService,
IStringLocalizer stringLocalizer,
InvoiceActivator invoiceActivator,
@@ -97,7 +95,6 @@ namespace BTCPayServer
_linkGenerator = linkGenerator;
_lightningAddressService = lightningAddressService;
_pullPaymentHostedService = pullPaymentHostedService;
- _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_pluginHookService = pluginHookService;
_invoiceActivator = invoiceActivator;
StringLocalizer = stringLocalizer;
@@ -470,7 +467,7 @@ namespace BTCPayServer
public async Task<IActionResult> GetLNURLForLightningAddress(string cryptoCode, string username, [FromQuery] long? amount = null, string comment = null)
{
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
- if (lightningAddressSettings is null || username is null)
+ if (lightningAddressSettings is null)
return NotFound(StringLocalizer["Unknown username"]);
var blob = lightningAddressSettings.GetBlob();
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
@@ -594,7 +591,7 @@ namespace BTCPayServer
var paymentMethodDetails = handler.ParsePaymentPromptDetails(pm.Details);
bool updatePaymentMethodDetails = false;
List<string> searchTerms = new List<string>();
- if (lnUrlMetadata?.TryGetValue("text/identifier", out var lnAddress) is true && lnAddress is not null)
+ if (lnUrlMetadata.TryGetValue("text/identifier", out var lnAddress) && lnAddress is not null)
{
paymentMethodDetails.ConsumedLightningAddress = lnAddress;
searchTerms.Add(lnAddress);
@@ -611,7 +608,7 @@ namespace BTCPayServer
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
action: nameof(GetLNURLForInvoice),
controller: "UILNURL",
- values: new { cryptoCode, invoiceId = i.Id }, Request.Scheme, Request.Host, Request.PathBase));
+ values: new { cryptoCode, invoiceId = i.Id }, Request.GetRequestBaseUrl()));
lnurlRequest.Metadata = JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
if (i.Type != InvoiceType.TopUp)
{
@@ -703,16 +700,14 @@ namespace BTCPayServer
return NotFound();
var handler = ((LNURLPayPaymentHandler)_handlers[pmi]);
var lightningPaymentMethod = i.GetPaymentPrompt(pmi);
- var promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
- if (promptDetails is null)
+ if (lightningPaymentMethod is null)
{
if (!await _invoiceActivator.ActivateInvoicePaymentMethod(i.Id, pmi))
return NotFound();
i = await _invoiceRepository.GetInvoice(invoiceId, true);
- lightningPaymentMethod = i.GetPaymentPrompt(pmi);
- promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
+ lightningPaymentMethod = i.GetPaymentPrompt(pmi)!;
}
-
+ var promptDetails = handler.ParsePaymentPromptDetails(lightningPaymentMethod.Details);
var lnConfig = _handlers.GetLightningConfig(store, network);
if (lnConfig is null)
return NotFound();
diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs
index 6f9ff05..1dd986d 100644
--- a/BTCPayServer/Controllers/UIPaymentRequestController.cs
+++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs
@@ -673,7 +673,7 @@ namespace BTCPayServer.Controllers
private StoreData GetCurrentStore() => HttpContext.GetStoreData();
- private PaymentRequestData GetCurrentPaymentRequest() => HttpContext.GetPaymentRequestData();
+ private PaymentRequestData GetCurrentPaymentRequest() => HttpContext.GetPaymentRequestDataOrNull();
private IActionResult NoPaymentMethodResult(string storeId)
{
diff --git a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
index 927ce92..52c0811 100644
--- a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
+++ b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs
@@ -45,7 +45,7 @@ namespace BTCPayServer.Controllers
{
get
{
- return HttpContext.GetStoreData();
+ return HttpContext.GetStoreDataOrNull();
}
}
@@ -289,7 +289,8 @@ namespace BTCPayServer.Controllers
if (vm is null)
return NotFound();
- vm.PayoutMethods = _payoutHandlers.GetSupportedPayoutMethods(HttpContext.GetStoreData());
+ var store = HttpContext.GetStoreData();
+ vm.PayoutMethods = _payoutHandlers.GetSupportedPayoutMethods(store);
vm.HasPayoutProcessor = await HasPayoutProcessor(storeId, vm.PayoutMethodId);
var payoutMethodId = PayoutMethodId.Parse(vm.PayoutMethodId);
var handler = _payoutHandlers
diff --git a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
index c8e7e0f..d79f676 100644
--- a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs
@@ -74,7 +74,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult LightningBalance(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
return store != null
? ViewComponent("StoreLightningBalance", new { Store = store, CryptoCode = cryptoCode })
: NotFound();
@@ -84,7 +84,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult StoreNumbers(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
return store != null
? ViewComponent("StoreNumbers", new { Store = store, CryptoCode = cryptoCode })
: NotFound();
@@ -94,7 +94,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult RecentTransactions(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
return store != null
? ViewComponent("StoreRecentTransactions", new { Store = store, CryptoCode = cryptoCode })
: NotFound();
@@ -104,7 +104,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult RecentInvoices(string storeId)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
return store != null
? ViewComponent("StoreRecentInvoices", new { Store = store })
: NotFound();
diff --git a/BTCPayServer/Controllers/UIStoresController.LightningLike.cs b/BTCPayServer/Controllers/UIStoresController.LightningLike.cs
index fa6ca9e..87eb77b 100644
--- a/BTCPayServer/Controllers/UIStoresController.LightningLike.cs
+++ b/BTCPayServer/Controllers/UIStoresController.LightningLike.cs
@@ -28,7 +28,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult Lightning(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -90,7 +90,7 @@ public partial class UIStoresController
[HttpGet("{storeId}/lightning/{cryptoCode}/dashboard/balance")]
public IActionResult LightningBalanceDashboard(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -101,7 +101,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> LightningBalanceDashboard(string storeId, string cryptoCode, HistogramType type)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
var lightningClient = await GetLightningClient(store, cryptoCode);
@@ -116,7 +116,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult SetupLightningNode(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -134,7 +134,7 @@ public partial class UIStoresController
public async Task<IActionResult> SetupLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
{
vm.CryptoCode = cryptoCode;
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -149,7 +149,7 @@ public partial class UIStoresController
return View(vm);
}
-
+
var paymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
LightningPaymentMethodConfig? paymentMethod;
@@ -223,7 +223,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult LightningSettings(string storeId, string cryptoCode)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -266,7 +266,7 @@ public partial class UIStoresController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> LightningSettings(LightningSettingsViewModel vm)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
@@ -279,21 +279,21 @@ public partial class UIStoresController
var network = _explorerProvider.GetNetwork(vm.CryptoCode);
var lnId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
var lnurlId = PaymentTypes.LNURL.GetPaymentMethodId(network.CryptoCode);
-
+
var lightning = GetConfig<LightningPaymentMethodConfig>(lnId, store);
if (lightning == null)
return NotFound();
-
+
var needUpdate = false;
var blob = store.GetStoreBlob();
blob.LightningDescriptionTemplate = vm.LightningDescriptionTemplate ?? string.Empty;
blob.LightningAmountInSatoshi = vm.LightningAmountInSatoshi;
blob.LightningPrivateRouteHints = vm.LightningPrivateRouteHints;
blob.OnChainWithLnInvoiceFallback = vm.OnChainWithLnInvoiceFallback;
-
+
// Lightning
blob.SetExcluded(lnId, !vm.Enabled);
-
+
// LNURL
blob.SetExcluded(lnurlId, !vm.LNURLEnabled || !vm.Enabled);
diff --git a/BTCPayServer/Controllers/UIStoresController.Onchain.cs b/BTCPayServer/Controllers/UIStoresController.Onchain.cs
index 82ada50..4d42ae8 100644
--- a/BTCPayServer/Controllers/UIStoresController.Onchain.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Onchain.cs
@@ -702,7 +702,7 @@ public partial class UIStoresController
private ActionResult IsAvailable(string cryptoCode, out StoreData store, out BTCPayNetwork network)
{
- store = HttpContext.GetStoreData();
+ store = HttpContext.GetStoreDataOrNull();
network = cryptoCode == null ? null : _explorerProvider.GetNetwork(cryptoCode);
return store == null || network == null ? NotFound() : null;
}
diff --git a/BTCPayServer/Controllers/UIStoresController.Settings.cs b/BTCPayServer/Controllers/UIStoresController.Settings.cs
index 05f06e8..76b088d 100644
--- a/BTCPayServer/Controllers/UIStoresController.Settings.cs
+++ b/BTCPayServer/Controllers/UIStoresController.Settings.cs
@@ -22,7 +22,7 @@ public partial class UIStoresController
[HttpGet("{storeId}/settings")]
public async Task<IActionResult> GeneralSettings(string storeId)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null) return NotFound();
var storeBlob = store.GetStoreBlob();
diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs
index cccc5ea..1492bc5 100644
--- a/BTCPayServer/Controllers/UIStoresController.cs
+++ b/BTCPayServer/Controllers/UIStoresController.cs
@@ -148,7 +148,7 @@ public partial class UIStoresController : Controller
return Forbid();
}
- public StoreData CurrentStore => HttpContext.GetStoreDataOrThrow();
+ public StoreData CurrentStore => HttpContext.GetStoreData();
public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData)
{
diff --git a/BTCPayServer/Controllers/UIUserStoresController.cs b/BTCPayServer/Controllers/UIUserStoresController.cs
index 0459d24..1a71346 100644
--- a/BTCPayServer/Controllers/UIUserStoresController.cs
+++ b/BTCPayServer/Controllers/UIUserStoresController.cs
@@ -116,7 +116,7 @@ namespace BTCPayServer.Controllers
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public IActionResult DeleteStore(string storeId)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
return View("Confirm", new ConfirmModel(StringLocalizer["Delete store {0}", store.StoreName], StringLocalizer["This store will still be accessible to users sharing it"], StringLocalizer["Delete"]));
@@ -126,7 +126,7 @@ namespace BTCPayServer.Controllers
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public async Task<IActionResult> DeleteStorePost(string storeId)
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
await _repo.RemoveStore(storeId, User.GetId());
diff --git a/BTCPayServer/Controllers/UIWalletsController.cs b/BTCPayServer/Controllers/UIWalletsController.cs
index adf3eed..47b27d0 100644
--- a/BTCPayServer/Controllers/UIWalletsController.cs
+++ b/BTCPayServer/Controllers/UIWalletsController.cs
@@ -1977,7 +1977,7 @@ namespace BTCPayServer.Controllers
private string? GetUserId() => User.GetIdOrNull();
- private StoreData GetCurrentStore() => HttpContext.GetStoreDataOrThrow();
+ private StoreData GetCurrentStore() => HttpContext.GetStoreData();
}
public class WalletReceiveViewModel
diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs
index f157edc..204d526 100644
--- a/BTCPayServer/Extensions.cs
+++ b/BTCPayServer/Extensions.cs
@@ -783,29 +783,29 @@ namespace BTCPayServer
public static IDisposable SwitchStoreData(this HttpContext ctx, StoreData? storeData)
{
- var old = ctx.GetStoreData();
+ var old = ctx.GetStoreDataOrNull();
ctx.SetStoreData(storeData);
return new ActionDisposable(() => { ctx.SetStoreData(old); });
}
/// <summary>
- /// Set after authorization succeed. If your route is authorized, this is guaranted to not be null.
+ /// Set after authorization succeed. If your route is authorized, this is guaranteed to not be null.
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
- public static StoreData? GetStoreData(this HttpContext ctx)
+ public static StoreData? GetStoreDataOrNull(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.STOREDATA") as StoreData;
/// <summary>
- /// Set after authorization succeed. If your route is authorized, this is guaranted to not throw.
+ /// Set after authorization succeed. If your route is authorized, this is guaranteed to not throw.
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
- public static StoreData GetStoreDataOrThrow(this HttpContext ctx)
- => GetStoreData(ctx) ?? throw new InvalidOperationException("StoreData is not set");
+ public static StoreData GetStoreData(this HttpContext ctx)
+ => GetStoreDataOrNull(ctx) ?? throw new InvalidOperationException("StoreData is not set");
public static void SetStoreData(this HttpContext ctx, StoreData? storeData)
=> ctx.Items["BTCPAY.STOREDATA"] = storeData;
public static string? GetCurrentStoreId(this HttpContext ctx)
- => GetStoreData(ctx)?.Id;
+ => GetStoreDataOrNull(ctx)?.Id;
public static StoreData[] GetStoresData(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[] ?? Array.Empty<StoreData>();
@@ -816,14 +816,14 @@ namespace BTCPayServer
/// </summary>
/// <param name="ctx"></param>
/// <returns></returns>
- public static InvoiceEntity? GetInvoiceData(this HttpContext ctx)
+ public static InvoiceEntity? GetInvoiceDataOrNull(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.INVOICEDATA") as InvoiceEntity;
public static void SetInvoiceData(this HttpContext ctx, InvoiceEntity? invoiceEntity)
=> ctx.Items["BTCPAY.INVOICEDATA"] = invoiceEntity;
- public static PaymentRequestData? GetPaymentRequestData(this HttpContext ctx)
+ public static PaymentRequestData? GetPaymentRequestDataOrNull(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.PAYMENTREQUESTDATA") as PaymentRequestData;
public static void SetPaymentRequestData(this HttpContext ctx, PaymentRequestData? paymentRequestData)
@@ -831,7 +831,7 @@ namespace BTCPayServer
ctx.Items["BTCPAY.PAYMENTREQUESTDATA"] = paymentRequestData;
}
- public static AppData? GetAppData(this HttpContext ctx)
+ public static AppData? GetAppDataOrNull(this HttpContext ctx)
=> ctx.Items.TryGet("BTCPAY.APPDATA") as AppData;
public static void SetAppData(this HttpContext ctx, AppData? appData)
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 7e5a072..0b7359e 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -502,8 +502,11 @@ namespace BTCPayServer.Hosting
{
new("appId", "SELECT \"StoreDataId\" FROM \"Apps\" WHERE \"Id\" = @id"),
new("payReqId", "SELECT \"StoreDataId\" FROM \"PaymentRequests\" WHERE \"Id\" = @id"),
+ new("paymentRequestId", "SELECT \"StoreDataId\" FROM \"PaymentRequests\" WHERE \"Id\" = @id"),
+ new("pullPaymentId", "SELECT \"StoreId\" FROM \"PullPayments\" WHERE \"Id\" = @id"),
new("invoiceId", "SELECT \"StoreDataId\" FROM \"Invoices\" WHERE \"Id\" = @id"),
})
+
services.AddSingleton(routeDataToStoreId);
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs
index 78ddb7e..47bda34 100644
--- a/BTCPayServer/Hosting/Startup.cs
+++ b/BTCPayServer/Hosting/Startup.cs
@@ -162,6 +162,7 @@ namespace BTCPayServer.Hosting
if (!Configuration.GetOrDefault<bool>("nocsp", false))
o.Filters.Add(new ContentSecurityPolicyAttribute(CSPTemplate.AntiXSS));
o.Filters.Add(new JsonHttpExceptionFilter());
+ // Note: Plugins should rather put controller-specific filters rather than this global one
o.Filters.Add<Security.SetContextFilter>();
o.Filters.Add(new JsonObjectExceptionFilter());
o.Filters.Add(new UIControllerAntiforgeryTokenAttribute());
diff --git a/BTCPayServer/PaymentRequest/PaymentRequestService.cs b/BTCPayServer/PaymentRequest/PaymentRequestService.cs
index 0f89327..d3112d5 100644
--- a/BTCPayServer/PaymentRequest/PaymentRequestService.cs
+++ b/BTCPayServer/PaymentRequest/PaymentRequestService.cs
@@ -84,12 +84,14 @@ namespace BTCPayServer.PaymentRequest
{
var pr = await _paymentRequestRepository.FindPaymentRequest(id, userId);
if (pr == null)
- {
return null;
- }
+ return await AsViewModel(pr);
+ }
+ public async Task<ViewPaymentRequestViewModel> AsViewModel(PaymentRequestData pr)
+ {
var blob = pr.GetBlob();
- var invoices = await _paymentRequestRepository.GetInvoicesForPaymentRequest(id);
+ var invoices = await _paymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
var paymentStats = _invoiceRepository.GetContributionsByPaymentMethodId(pr.Currency, invoices, true);
var amountDue = pr.Amount - paymentStats.Total;
var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
@@ -111,26 +113,26 @@ namespace BTCPayServer.PaymentRequest
PendingInvoiceHasPayments = pendingInvoice != null &&
pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None,
Invoices = new ViewPaymentRequestViewModel.InvoiceList(invoices.Select(entity =>
- {
- var state = entity.GetInvoiceState();
- var payments = ViewPaymentRequestViewModel.PaymentRequestInvoicePayment.GetViewModels(entity, _displayFormatter, _transactionLinkProviders, _handlers);
+ {
+ var state = entity.GetInvoiceState();
+ var payments = ViewPaymentRequestViewModel.PaymentRequestInvoicePayment.GetViewModels(entity, _displayFormatter, _transactionLinkProviders, _handlers);
- if (state.Status is InvoiceStatus.Invalid or InvoiceStatus.Expired && payments.Count is 0)
- return null;
+ if (state.Status is InvoiceStatus.Invalid or InvoiceStatus.Expired && payments.Count is 0)
+ return null;
- return new ViewPaymentRequestViewModel.PaymentRequestInvoice
- {
- Id = entity.Id,
- Amount = entity.Price,
- AmountFormatted = _displayFormatter.Currency(entity.Price, pr.Currency, DisplayFormatter.CurrencyFormat.Symbol),
- Currency = entity.Currency,
- ExpiryDate = entity.ExpirationTime.DateTime,
- State = state,
- StateFormatted = state.ToString(),
- Payments = payments
- };
- })
- .Where(invoice => invoice != null))
+ return new ViewPaymentRequestViewModel.PaymentRequestInvoice
+ {
+ Id = entity.Id,
+ Amount = entity.Price,
+ AmountFormatted = _displayFormatter.Currency(entity.Price, pr.Currency, DisplayFormatter.CurrencyFormat.Symbol),
+ Currency = entity.Currency,
+ ExpiryDate = entity.ExpirationTime.DateTime,
+ State = state,
+ StateFormatted = state.ToString(),
+ Payments = payments
+ };
+ })
+ .Where(invoice => invoice != null))
};
}
}
diff --git a/BTCPayServer/Plugins/Bitpay/Controllers/BitpayInvoiceController.cs b/BTCPayServer/Plugins/Bitpay/Controllers/BitpayInvoiceController.cs
index 966f40a..b0e4b70 100644
--- a/BTCPayServer/Plugins/Bitpay/Controllers/BitpayInvoiceController.cs
+++ b/BTCPayServer/Plugins/Bitpay/Controllers/BitpayInvoiceController.cs
@@ -50,7 +50,10 @@ public class BitpayInvoiceController : ControllerBase
{
if (invoice == null)
throw new BitpayHttpException(400, "Invalid invoice");
- return await CreateInvoiceCore(invoice, HttpContext.GetStoreData(), HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
+ var store = HttpContext.GetStoreDataOrNull();
+ if (store == null)
+ throw new BitpayHttpException(404, "Store not found");
+ return await CreateInvoiceCore(invoice, store, HttpContext.Request.GetAbsoluteRoot(), cancellationToken: cancellationToken);
}
[HttpGet]
diff --git a/BTCPayServer/Plugins/Bitpay/Controllers/BitpayRateController.cs b/BTCPayServer/Plugins/Bitpay/Controllers/BitpayRateController.cs
index f965dcb..9913207 100644
--- a/BTCPayServer/Plugins/Bitpay/Controllers/BitpayRateController.cs
+++ b/BTCPayServer/Plugins/Bitpay/Controllers/BitpayRateController.cs
@@ -32,7 +32,7 @@ public class BitpayRateController : ControllerBase
readonly StoreRepository _storeRepo;
private readonly InvoiceRepository _invoiceRepository;
- private StoreData CurrentStore => HttpContext.GetStoreData();
+ private StoreData CurrentStore => HttpContext.GetStoreDataOrNull();
public BitpayRateController(
RateFetcher rateProviderFactory,
@@ -55,9 +55,16 @@ public class BitpayRateController : ControllerBase
[BitpayEndpointSelectorPolicy.BitpayEndpointMetadata]
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string cryptoCode = null, CancellationToken cancellationToken = default)
{
- var inv = _invoiceRepository.CreateNewInvoice(CurrentStore.Id);
+ var store = CurrentStore;
+ if (store == null)
+ {
+ var err = Ok(new BitpayErrorsModel { Error = "Store not found" });
+ err.StatusCode = 404;
+ return err;
+ }
+ var inv = _invoiceRepository.CreateNewInvoice(store.Id);
inv.Currency = baseCurrency;
- var ctx = new InvoiceCreationContext(CurrentStore, CurrentStore.GetStoreBlob(), inv, new Logging.InvoiceLogs(), _handlers, null);
+ var ctx = new InvoiceCreationContext(store, store.GetStoreBlob(), inv, new Logging.InvoiceLogs(), _handlers, null);
ctx.SetLazyActivation(true);
await ctx.BeforeFetchingRates();
var currencyCodes = ctx
diff --git a/BTCPayServer/Plugins/Bitpay/Controllers/UIStoresTokenController.cs b/BTCPayServer/Plugins/Bitpay/Controllers/UIStoresTokenController.cs
index 257a6f4..2f6e6ad 100644
--- a/BTCPayServer/Plugins/Bitpay/Controllers/UIStoresTokenController.cs
+++ b/BTCPayServer/Plugins/Bitpay/Controllers/UIStoresTokenController.cs
@@ -36,7 +36,7 @@ public class UIStoresTokenController(
PermissionService permissionService) : Controller
{
public IStringLocalizer StringLocalizer { get; } = stringLocalizer;
- public StoreData CurrentStore => HttpContext.GetStoreData() ?? throw new InvalidOperationException("Store not found");
+ public StoreData CurrentStore => HttpContext.GetStoreDataOrNull() ?? throw new InvalidOperationException("Store not found");
private string? GetUserId() => User.GetIdOrNull();
[TempData]
@@ -188,7 +188,7 @@ public class UIStoresTokenController(
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GenerateAPIKey(string storeId, string command = "")
{
- var store = HttpContext.GetStoreData();
+ var store = HttpContext.GetStoreDataOrNull();
if (store == null)
return NotFound();
if (command == "revoke")
diff --git a/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml b/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
index 91a871e..49dff9d 100644
--- a/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
+++ b/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
@@ -1,6 +1,6 @@
@model CreateTokenViewModel
@{
- var store = Context.GetStoreData();
+ var store = Context.GetStoreDataOrNull();
ViewData.SetLayoutModel(new(nameof(StoreNavPages.Tokens), StringLocalizer["Create New Token"]));
ViewBag.HidePublicKey ??= false;
ViewBag.ShowStores ??= false;
diff --git a/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml b/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
index 597c85d..d43aca5 100644
--- a/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
+++ b/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
@@ -1,7 +1,7 @@
@using BTCPayServer.Client
@model PairingModel
@{
- var store = Context.GetStoreData();
+ var store = Context.GetStoreDataOrNull();
Layout = store is null ? "_LayoutWizard" : "_Layout";
ViewData.SetLayoutModel(new(nameof(StoreNavPages.Tokens), StringLocalizer["Pairing Permission"]));
}
diff --git a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs
index 1218229..49a563c 100644
--- a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs
+++ b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs
@@ -583,7 +583,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
return currency.Trim().ToUpperInvariant();
}
- private AppData GetCurrentApp() => HttpContext.GetAppData();
+ private AppData GetCurrentApp() => HttpContext.GetAppDataOrNull();
private string GetUserId() => User.GetIdOrNull();
diff --git a/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs b/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs
index 74c6bf3..682abb4 100644
--- a/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs
+++ b/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs
@@ -92,6 +92,6 @@ namespace BTCPayServer.Plugins.PayButton.Controllers
});
}
- private StoreData GetCurrentStore => HttpContext.GetStoreDataOrThrow();
+ private StoreData GetCurrentStore => HttpContext.GetStoreData();
}
}
diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs
index a2a019c..b87652b 100644
--- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs
+++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs
@@ -740,7 +740,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
private StoreData GetCurrentStore() => HttpContext.GetStoreData();
- private AppData GetCurrentApp() => HttpContext.GetAppData();
+ private AppData GetCurrentApp() => HttpContext.GetAppDataOrNull();
private async Task FillUsers(UpdatePointOfSaleViewModel vm)
{
diff --git a/BTCPayServer/Plugins/Shopify/UIShopifyController.cs b/BTCPayServer/Plugins/Shopify/UIShopifyController.cs
index 364b1c2..d68b4a2 100644
--- a/BTCPayServer/Plugins/Shopify/UIShopifyController.cs
+++ b/BTCPayServer/Plugins/Shopify/UIShopifyController.cs
@@ -66,13 +66,7 @@ namespace BTCPayServer.Plugins.Shopify
_jsonHelper = jsonHelper;
_clientFactory = clientFactory;
}
- public StoreData CurrentStore
- {
- get
- {
- return this.HttpContext.GetStoreData();
- }
- }
+ public StoreData CurrentStore => this.HttpContext.GetStoreData();
private static string _cachedShopifyJavascript;
private async Task<string> GetJavascript()
diff --git a/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs b/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs
index 29f31e5..b2071a7 100644
--- a/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs
+++ b/BTCPayServer/Plugins/Subscriptions/Controllers/UIOfferingController.cs
@@ -488,7 +488,7 @@ public partial class UIOfferingController(
OfferingId = offeringId,
PlanId = planId,
OfferingName = offering.App.Name,
- Currency = plan?.Currency ?? this.HttpContext.GetStoreDataOrThrow().GetStoreBlob().DefaultCurrency,
+ Currency = plan?.Currency ?? this.HttpContext.GetStoreData().GetStoreBlob().DefaultCurrency,
Price = plan?.Price ?? 0m,
Name = plan?.Name ?? "",
Description = plan?.Description ?? "",
diff --git a/BTCPayServer/Plugins/Subscriptions/SubscriptionsPlugin.cs b/BTCPayServer/Plugins/Subscriptions/SubscriptionsPlugin.cs
index 7832e2f..71a3e8b 100644
--- a/BTCPayServer/Plugins/Subscriptions/SubscriptionsPlugin.cs
+++ b/BTCPayServer/Plugins/Subscriptions/SubscriptionsPlugin.cs
@@ -9,6 +9,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Plugins.Emails.Views;
using BTCPayServer.Plugins.Subscriptions.Controllers;
+using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Views.UIStoreMembership;
@@ -34,6 +35,10 @@ public class SubscriptionsPlugin : BaseBTCPayServerPlugin
services.AddSingleton<SubscriptionHostedService>();
services.AddSingleton<IHostedService>(s => s.GetRequiredService<SubscriptionHostedService>());
+ services.AddSingleton(new BuiltInPermissionScopeProvider.RouteValueToStoreIdQuery(
+ "offeringId", "SELECT a.\"StoreDataId\" FROM \"Apps\" a JOIN subs_offerings o ON o.app_id=a.\"Id\" WHERE o.id=@id"
+ ));
+
services.AddScheduledDbScript("Portal Session Cleanup",
"""
WITH expired_portal_session AS (
diff --git a/BTCPayServer/Plugins/Webhooks/Controllers/GreenfieldStoreWebhooksController.cs b/BTCPayServer/Plugins/Webhooks/Controllers/GreenfieldStoreWebhooksController.cs
index f6c73f7..50aa86e 100644
--- a/BTCPayServer/Plugins/Webhooks/Controllers/GreenfieldStoreWebhooksController.cs
+++ b/BTCPayServer/Plugins/Webhooks/Controllers/GreenfieldStoreWebhooksController.cs
@@ -47,13 +47,7 @@ namespace BTCPayServer.Plugins.Webhooks.Controllers
}
}
- string CurrentStoreId
- {
- get
- {
- return this.HttpContext.GetStoreData()?.Id;
- }
- }
+ string CurrentStoreId => this.HttpContext.GetStoreData().Id;
[HttpPost("~/api/v1/stores/{storeId}/webhooks")]
public async Task<IActionResult> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create)
diff --git a/BTCPayServer/Plugins/Webhooks/Controllers/UIStoreWebhooksController.cs b/BTCPayServer/Plugins/Webhooks/Controllers/UIStoreWebhooksController.cs
index 2cf60fd..04a8c13 100644
--- a/BTCPayServer/Plugins/Webhooks/Controllers/UIStoreWebhooksController.cs
+++ b/BTCPayServer/Plugins/Webhooks/Controllers/UIStoreWebhooksController.cs
@@ -24,7 +24,7 @@ public class UIStoreWebhooksController(
IStringLocalizer stringLocalizer,
WebhookSender webhookSender) : Controller
{
- public Data.StoreData CurrentStore => HttpContext.GetStoreDataOrThrow();
+ public Data.StoreData CurrentStore => HttpContext.GetStoreData();
public IStringLocalizer StringLocalizer { get; set; } = stringLocalizer;
private async Task<Data.WebhookDeliveryData?> LastDeliveryForWebhook(string webhookId)
{
diff --git a/BTCPayServer/Security/BuiltInPermissionScopeProvider.cs b/BTCPayServer/Security/BuiltInPermissionScopeProvider.cs
index f93f6f8..97b3bce 100644
--- a/BTCPayServer/Security/BuiltInPermissionScopeProvider.cs
+++ b/BTCPayServer/Security/BuiltInPermissionScopeProvider.cs
@@ -103,9 +103,7 @@ public class BuiltInPermissionScopeProvider(
// Consider the route /stores/{storeId}/apps/{appId}
// This check is making sure that the `storeId` is matching the scope resolved from `appId`.
if (storeId2 != storeId)
- {
storeId2 = null;
- }
if (storeId2 is not null)
additionalScopes.Add(new AdditionalScope(i.RouteValue, id));
diff --git a/BTCPayServer/Security/SetContextFilter.cs b/BTCPayServer/Security/SetContextFilter.cs
index b4ec842..f0cc2b1 100644
--- a/BTCPayServer/Security/SetContextFilter.cs
+++ b/BTCPayServer/Security/SetContextFilter.cs
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
namespace BTCPayServer.Security;
+// Note: Plugins should rather put controller-specific filters rather than this global one
public class SetContextFilter(
PaymentRequestRepository paymentRequestRepository,
InvoiceRepository invoiceRepository,
@@ -44,6 +45,7 @@ public class SetContextFilter(
if (httpContext.Items.TryGetValue(BuiltInPermissionHandler.StoresKey, out var ooo) && ooo is StoreData[] stores)
httpContext.SetStoresData(stores);
+ //TODO: We should probably do this on controller specific filters, this would be better example for plugins
if (httpContext.Items.TryGetValue(BuiltInPermissionScopeProvider.AdditionalScopeKey, out var o) && o is IEnumerable<BuiltInPermissionScopeProvider.AdditionalScope> additionalScopes)
{
foreach (var additionalScope in additionalScopes)
@@ -55,7 +57,7 @@ public class SetContextFilter(
if (app is not null)
httpContext.SetAppData(app);
break;
- case "payReqId":
+ case "payReqId" or "paymentRequestId":
var paymentRequest = await paymentRequestRepository.FindPaymentRequest(additionalScope.Scope, userId);
if (paymentRequest is not null)
httpContext.SetPaymentRequestData(paymentRequest);
diff --git a/BTCPayServer/Views/Shared/LayoutHead.cshtml b/BTCPayServer/Views/Shared/LayoutHead.cshtml
index 179f8cd..dd9615e 100644
--- a/BTCPayServer/Views/Shared/LayoutHead.cshtml
+++ b/BTCPayServer/Views/Shared/LayoutHead.cshtml
@@ -3,7 +3,7 @@
@inject UriResolver UriResolver
@{
ViewData.TryGetValue("StoreBranding", out var storeBranding);
- var store = Context.GetStoreData();
+ var store = Context.GetStoreDataOrNull();
var storeBlob = store?.GetStoreBlob();
var isBackend = store != null && storeBranding == null;
if (isBackend && storeBlob.ApplyBrandColorToBackend)
diff --git a/BTCPayServer/Views/UIStores/Dashboard.cshtml b/BTCPayServer/Views/UIStores/Dashboard.cshtml
index 05c7063..f3d7f4a 100644
--- a/BTCPayServer/Views/UIStores/Dashboard.cshtml
+++ b/BTCPayServer/Views/UIStores/Dashboard.cshtml
@@ -3,7 +3,7 @@
@{
BTCPayServer.Plugins.PluginExceptionHandler.SetDisablePluginIfCrash(Context);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.Dashboard), Model.StoreName));
- var store = ViewContext.HttpContext.GetStoreData();
+ var store = ViewContext.HttpContext.GetStoreDataOrNull();
}
<partial name="_StatusMessage" />
Why this scored 64/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.