diff --git a/BTCPayServer.Client/Models/PaymentRequestBaseData.cs b/BTCPayServer.Client/Models/PaymentRequestBaseData.cs index d425f61d0..6803d7833 100644 --- a/BTCPayServer.Client/Models/PaymentRequestBaseData.cs +++ b/BTCPayServer.Client/Models/PaymentRequestBaseData.cs @@ -37,6 +37,10 @@ namespace BTCPayServer.Client.Models public string Title { get; set; } public string Description { get; set; } public string Email { get; set; } + /// + /// Linking to invoices outside BTCPay Server using & user defined ids + /// + public string ReferenceId { get; set; } public bool AllowCustomPaymentAmounts { get; set; } [JsonConverter(typeof(StringEnumConverter))] diff --git a/BTCPayServer.Client/Models/WebhookEventType.cs b/BTCPayServer.Client/Models/WebhookEventType.cs index ed2d431c4..b7c1e6bcb 100644 --- a/BTCPayServer.Client/Models/WebhookEventType.cs +++ b/BTCPayServer.Client/Models/WebhookEventType.cs @@ -17,5 +17,6 @@ public static class WebhookEventType public const string PaymentRequestCreated = nameof(PaymentRequestCreated); public const string PaymentRequestArchived = nameof(PaymentRequestArchived); public const string PaymentRequestStatusChanged = nameof(PaymentRequestStatusChanged); + public const string PaymentRequestCompleted = nameof(PaymentRequestCompleted); } diff --git a/BTCPayServer.Data/Data/PaymentRequestData.cs b/BTCPayServer.Data/Data/PaymentRequestData.cs index da4c3df27..943e64dc2 100644 --- a/BTCPayServer.Data/Data/PaymentRequestData.cs +++ b/BTCPayServer.Data/Data/PaymentRequestData.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using BTCPayServer.Client.Models; using Microsoft.EntityFrameworkCore; @@ -16,6 +17,11 @@ namespace BTCPayServer.Data public string Currency { get; set; } public decimal Amount { get; set; } + /// + /// Linking to invoices outside BTCPay Server using & user defined ids + /// + public string ReferenceId { get; set; } + public StoreData StoreData { get; set; } public Client.Models.PaymentRequestStatus Status { get; set; } diff --git a/BTCPayServer.Data/Migrations/20250415235643_AddingReferenceIdToPaymentRequest.cs b/BTCPayServer.Data/Migrations/20250415235643_AddingReferenceIdToPaymentRequest.cs new file mode 100644 index 000000000..f32b26812 --- /dev/null +++ b/BTCPayServer.Data/Migrations/20250415235643_AddingReferenceIdToPaymentRequest.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BTCPayServer.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250407133937_AddingReferenceIdToPaymentRequest")] + public partial class AddingReferenceIdToPaymentRequest : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ReferenceId", + table: "PaymentRequests", + type: "TEXT", + nullable: true); + migrationBuilder.Sql(""" + CREATE UNIQUE INDEX IX_PaymentRequests_StoreDataId_ReferenceId + ON "PaymentRequests" ("StoreDataId", "ReferenceId") + WHERE "ReferenceId" IS NOT NULL; + """); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ReferenceId", + table: "PaymentRequests"); + } + } +} diff --git a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs index 00fa37212..45746b1c2 100644 --- a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using System.Collections.Generic; using BTCPayServer.Data; @@ -544,6 +544,9 @@ namespace BTCPayServer.Migrations b.Property("Expiry") .HasColumnType("timestamp with time zone"); + b.Property("ReferenceId") + .HasColumnType("text"); + b.Property("Status") .IsRequired() .HasColumnType("text"); diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index e70a17210..f559d91a3 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -2048,7 +2048,7 @@ namespace BTCPayServer.Tests new() { Title = "A", Currency = "helloinvalid", Amount = 1 }); }); var newPaymentRequest = await client.CreatePaymentRequest(user.StoreId, - new() { Title = "A", Currency = "USD", Amount = 1 }); + new() { Title = "A", Currency = "USD", Amount = 1, ReferenceId = "1234"}); //list payment request var paymentRequests = await viewOnly.GetPaymentRequests(user.StoreId); @@ -2061,10 +2061,12 @@ namespace BTCPayServer.Tests var paymentRequest = await viewOnly.GetPaymentRequest(user.StoreId, newPaymentRequest.Id); Assert.Equal(newPaymentRequest.Title, paymentRequest.Title); Assert.Equal(newPaymentRequest.StoreId, user.StoreId); + Assert.Equal(newPaymentRequest.ReferenceId, paymentRequest.ReferenceId); //update payment request var updateRequest = paymentRequest; updateRequest.Title = "B"; + updateRequest.ReferenceId = "EmperorNicolasGeneralRockstar"; await AssertHttpError(403, async () => { await viewOnly.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest); @@ -2072,6 +2074,7 @@ namespace BTCPayServer.Tests await client.UpdatePaymentRequest(user.StoreId, paymentRequest.Id, updateRequest); paymentRequest = await client.GetPaymentRequest(user.StoreId, newPaymentRequest.Id); Assert.Equal(updateRequest.Title, paymentRequest.Title); + Assert.Equal(updateRequest.ReferenceId, paymentRequest.ReferenceId); //archive payment request await AssertHttpError(403, async () => diff --git a/BTCPayServer.Tests/PaymentRequestTests.cs b/BTCPayServer.Tests/PaymentRequestTests.cs index 98fa378cd..2a7322881 100644 --- a/BTCPayServer.Tests/PaymentRequestTests.cs +++ b/BTCPayServer.Tests/PaymentRequestTests.cs @@ -43,12 +43,20 @@ namespace BTCPayServer.Tests Currency = "BTC", Amount = 1, StoreId = user.StoreId, - Description = "description" + Description = "description", + ReferenceId = "custom-id-1" }; var id = Assert .IsType(await paymentRequestController.EditPaymentRequest(null, request)) .RouteValues.Values.Last().ToString(); + // Assert initial Title and ReferenceId + var repo = tester.PayTester.GetService(); + var prData = await repo.FindPaymentRequest(id, user.UserId); + Assert.NotNull(prData); + Assert.Equal("original juice", prData.GetBlob().Title); + Assert.Equal("custom-id-1", prData.ReferenceId); + paymentRequestController.HttpContext.SetPaymentRequestData(new PaymentRequestData { Id = id, StoreDataId = request.StoreId }); // Permission guard for guests editing @@ -56,8 +64,15 @@ namespace BTCPayServer.Tests .IsType(await guestpaymentRequestController.EditPaymentRequest(user.StoreId, id)); request.Title = "update"; + request.ReferenceId = "custom-id-2"; Assert.IsType(await paymentRequestController.EditPaymentRequest(id, request)); + // Assert updated Title and ReferenceId + prData = await repo.FindPaymentRequest(id, user.UserId); + Assert.NotNull(prData); + Assert.Equal("update", prData.GetBlob().Title); + Assert.Equal("custom-id-2", prData.ReferenceId); + Assert.Equal(request.Title, Assert.IsType(Assert .IsType(await paymentRequestController.ViewPaymentRequest(id)).Model).Title); diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs index 102493cae..b61deb1b6 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldPaymentRequestsController.cs @@ -205,7 +205,7 @@ namespace BTCPayServer.Controllers.Greenfield pr = new PaymentRequestData() { StoreDataId = storeId, - Status = Client.Models.PaymentRequestStatus.Pending, + Status = PaymentRequestStatus.Pending, Created = DateTimeOffset.UtcNow, Amount = request.Amount, Currency = request.Currency ?? StoreData.GetStoreBlob().DefaultCurrency, @@ -213,17 +213,19 @@ namespace BTCPayServer.Controllers.Greenfield }; } + pr.ReferenceId = string.IsNullOrEmpty(request.ReferenceId) ? null : request.ReferenceId; + if (!ModelState.IsValid) return this.CreateValidationError(ModelState); var blob = pr.GetBlob(); pr.SetBlob(new() { + Title = request.Title, AllowCustomPaymentAmounts = request.AllowCustomPaymentAmounts, Description = request.Description, Email = request.Email, FormId = request.FormId, - Title = request.Title, FormResponse = blob.FormId != request.FormId ? null : blob.FormResponse }); pr = await _paymentRequestRepository.CreateOrUpdatePaymentRequest(pr); @@ -251,6 +253,7 @@ namespace BTCPayServer.Controllers.Greenfield Title = blob.Title, ExpiryDate = data.Expiry, Email = blob.Email, + ReferenceId = data.ReferenceId, AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts, FormResponse = blob.FormResponse, FormId = blob.FormId diff --git a/BTCPayServer/Controllers/UIInvoiceController.cs b/BTCPayServer/Controllers/UIInvoiceController.cs index 30dd5d40f..80d39df45 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.cs @@ -145,7 +145,7 @@ namespace BTCPayServer.Controllers amount = amountDue; var redirectUrl = _linkGenerator.PaymentRequestLink(id, request.Scheme, request.Host, request.PathBase); - JObject invoiceMetadata = prData.GetBlob()?.FormResponse ?? new JObject(); + JObject invoiceMetadata = prBlob.FormResponse ?? new JObject(); invoiceMetadata.Merge(new InvoiceMetadata { OrderId = PaymentRequestRepository.GetOrderIdForPaymentRequest(id), @@ -163,7 +163,8 @@ namespace BTCPayServer.Controllers Checkout = { RedirectURL = redirectUrl }, Receipt = new InvoiceDataBase.ReceiptOptions { Enabled = false } }; - + if (prData.ReferenceId is not null or "") + invoiceRequest.AdditionalSearchTerms = [prData.ReferenceId]; var additionalTags = new List { PaymentRequestRepository.GetInternalTag(id) }; return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken); } diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs index 192347685..42a406910 100644 --- a/BTCPayServer/Controllers/UIPaymentRequestController.cs +++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs @@ -99,7 +99,8 @@ namespace BTCPayServer.Controllers Skip = model.Skip, Count = model.Count, Status = fs.GetFilterArray("status")?.Select(s => Enum.Parse(s, true)).ToArray(), - IncludeArchived = fs.GetFilterBool("includearchived") ?? false + IncludeArchived = fs.GetFilterBool("includearchived") ?? false, + SearchText = model.SearchText }); model.Search = fs; @@ -206,6 +207,7 @@ namespace BTCPayServer.Controllers data.Amount = viewModel.Amount; data.Currency = viewModel.Currency ?? store.GetStoreBlob().DefaultCurrency; data.Expiry = viewModel.ExpiryDate?.ToUniversalTime(); + data.ReferenceId = viewModel.ReferenceId; blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts; blob.FormId = viewModel.FormId; diff --git a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookDeliveryRequest.cs b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookDeliveryRequest.cs index 7130442d2..24c7d7fc1 100644 --- a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookDeliveryRequest.cs +++ b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookDeliveryRequest.cs @@ -47,7 +47,8 @@ public class PaymentRequestWebhookDeliveryRequest : WebhookSender.WebhookDeliver .Replace("{PaymentRequest.Currency}", data.Currency) .Replace("{PaymentRequest.Title}", blob.Title) .Replace("{PaymentRequest.Description}", blob.Description) - .Replace("{PaymentRequest.Status}", data.Status.ToString()); + .Replace("{PaymentRequest.ReferenceId}", data.ReferenceId) + .Replace("{PaymentRequest.Status}", _evt.Data.Status.ToString()); res = InterpolateJsonField(res, "PaymentRequest.FormResponse", blob.FormResponse); return res; diff --git a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs index c018b3ea4..4df461544 100644 --- a/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs +++ b/BTCPayServer/HostedServices/Webhooks/PaymentRequestWebhookProvider.cs @@ -22,6 +22,7 @@ public class PaymentRequestWebhookProvider: WebhookProvider {WebhookEventType.PaymentRequestUpdated, "Payment Request - Updated"}, {WebhookEventType.PaymentRequestArchived, "Payment Request - Archived"}, {WebhookEventType.PaymentRequestStatusChanged, "Payment Request - Status Changed"}, + {WebhookEventType.PaymentRequestCompleted, "Payment Request - Completed"}, }; } @@ -42,6 +43,7 @@ public class PaymentRequestWebhookProvider: WebhookProvider PaymentRequestEvent.Updated => new WebhookPaymentRequestEvent(WebhookEventType.PaymentRequestUpdated, evt.Data.StoreDataId), PaymentRequestEvent.Archived => new WebhookPaymentRequestEvent(WebhookEventType.PaymentRequestArchived, evt.Data.StoreDataId), PaymentRequestEvent.StatusChanged => new WebhookPaymentRequestEvent(WebhookEventType.PaymentRequestStatusChanged, evt.Data.StoreDataId), + PaymentRequestEvent.Completed => new WebhookPaymentRequestEvent(WebhookEventType.PaymentRequestCompleted, evt.Data.StoreDataId), _ => null }; } diff --git a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs index c1c000c44..0108c530d 100644 --- a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs +++ b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs @@ -48,6 +48,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels Description = blob.Description; ExpiryDate = data.Expiry?.UtcDateTime; Email = blob.Email; + ReferenceId = data.ReferenceId; AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts; FormResponse = blob.FormResponse is null ? null @@ -84,6 +85,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels [MailboxAddress] public string Email { get; set; } + [Display(Name = "Reference Id")] + public string ReferenceId { get; set; } + [Display(Name = "Allow payee to create invoices with custom amounts")] public bool AllowCustomPaymentAmounts { get; set; } @@ -106,6 +110,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels Description = blob.Description; ExpiryDate = data.Expiry?.UtcDateTime; Email = blob.Email; + ReferenceId = data.ReferenceId; AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts; switch (data.Status) { @@ -127,6 +132,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels } } public StoreBrandingViewModel StoreBranding { get; set; } + public string ReferenceId { get; set; } public bool AllowCustomPaymentAmounts { get; set; } public string Email { get; set; } public string Status { get; set; } @@ -147,11 +153,11 @@ namespace BTCPayServer.Models.PaymentRequestViewModels #nullable enable public class InvoiceList : List { - static HashSet stateAllowedToDisplay = new HashSet - { - new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.None), - new InvoiceState(InvoiceStatus.New, InvoiceExceptionStatus.PaidPartial), - }; + private static HashSet stateAllowedToDisplay = + [ + new(InvoiceStatus.New, InvoiceExceptionStatus.None), + new(InvoiceStatus.New, InvoiceExceptionStatus.PaidPartial) + ]; public InvoiceList() { @@ -195,7 +201,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels public class PaymentRequestInvoicePayment { - public static List + public static List GetViewModels( InvoiceEntity invoice, DisplayFormatter displayFormatter, @@ -215,7 +221,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels string link = paymentMethodId is null ? null : txLinkProvider.GetTransactionLink(paymentMethodId, txId); - return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment + return new PaymentRequestInvoicePayment { Amount = paymentEntity.PaidAmount.Gross, Paid = paymentEntity.InvoicePaidAmount.Net, diff --git a/BTCPayServer/Services/CallbackGenerator.cs b/BTCPayServer/Services/CallbackGenerator.cs index 015af6280..3b0bb9f71 100644 --- a/BTCPayServer/Services/CallbackGenerator.cs +++ b/BTCPayServer/Services/CallbackGenerator.cs @@ -92,5 +92,29 @@ namespace BTCPayServer.Services pathBase: request.PathBase ) ?? throw Bug(); } + + public string PaymentRequestByIdLink(string payReqId, HttpRequest request) + { + return LinkGenerator.GetUriByAction( + action: nameof(UIPaymentRequestController.ViewPaymentRequest), + controller: "UIPaymentRequest", + values: new { payReqId }, + scheme: request.Scheme, + host: request.Host, + pathBase: request.PathBase + ) ?? throw Bug(); + } + + public string PaymentRequestListLink(string storeId, HttpRequest request) + { + return LinkGenerator.GetUriByAction( + action: nameof(UIPaymentRequestController.GetPaymentRequests), + controller: "UIPaymentRequest", + values: new { storeId }, + scheme: request.Scheme, + host: request.Host, + pathBase: request.PathBase + ) ?? throw Bug(); + } } } diff --git a/BTCPayServer/Services/PaymentRequests/PaymentRequestRepository.cs b/BTCPayServer/Services/PaymentRequests/PaymentRequestRepository.cs index a20271c82..652235c79 100644 --- a/BTCPayServer/Services/PaymentRequests/PaymentRequestRepository.cs +++ b/BTCPayServer/Services/PaymentRequests/PaymentRequestRepository.cs @@ -2,10 +2,12 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Stores; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.PaymentRequests { @@ -15,6 +17,7 @@ namespace BTCPayServer.Services.PaymentRequests public const string Updated = nameof(Updated); public const string Archived = nameof(Archived); public const string StatusChanged = nameof(StatusChanged); + public const string Completed = nameof(Completed); public PaymentRequestData Data { get; set; } public string Type { get; set; } } @@ -99,9 +102,7 @@ namespace BTCPayServer.Services.PaymentRequests { await using var context = _ContextFactory.CreateContext(); var paymentRequestData = await context.FindAsync(paymentRequestId); - if (paymentRequestData == null) - return; - if( paymentRequestData.Status == status) + if (paymentRequestData == null || paymentRequestData.Status == status) return; paymentRequestData.Status = status; @@ -112,6 +113,15 @@ namespace BTCPayServer.Services.PaymentRequests Data = paymentRequestData, Type = PaymentRequestEvent.StatusChanged }); + + if (status == PaymentRequestStatus.Completed) + { + _eventAggregator.Publish(new PaymentRequestEvent() + { + Data = paymentRequestData, + Type = PaymentRequestEvent.Completed + }); + } } public async Task GetExpirablePaymentRequests(CancellationToken cancellationToken = default) { @@ -126,48 +136,43 @@ namespace BTCPayServer.Services.PaymentRequests } public async Task FindPaymentRequests(PaymentRequestQuery query, CancellationToken cancellationToken = default) { - using var context = _ContextFactory.CreateContext(); - var queryable = context.PaymentRequests.Include(data => data.StoreData).AsQueryable(); + await using var context = _ContextFactory.CreateContext(); + IQueryable queryable = context.PaymentRequests.AsQueryable(); + + if (!string.IsNullOrEmpty(query.StoreId)) + queryable = queryable.Where(data => data.StoreDataId == query.StoreId); + + if (!string.IsNullOrEmpty(query.SearchText)) + { + if (string.IsNullOrEmpty(query.StoreId)) + throw new InvalidOperationException("PaymentRequestQuery.StoreId should be specified"); + // We are repeating the StoreId on purpose here, so Postgres can use the index + queryable = context.PaymentRequests.Where(p => (p.StoreDataId == query.StoreId && p.ReferenceId == query.SearchText) || p.Id == query.SearchText); + } + + queryable = queryable.Include(data => data.StoreData); if (!query.IncludeArchived) - { queryable = queryable.Where(data => !data.Archived); - } - if (!string.IsNullOrEmpty(query.StoreId)) - { - queryable = queryable.Where(data => - data.StoreDataId == query.StoreId); - } if (query.Status != null && query.Status.Any()) - { - queryable = queryable.Where(data => - query.Status.Contains(data.Status)); - } + queryable = queryable.Where(data => query.Status.Contains(data.Status)); if (query.Ids != null && query.Ids.Any()) - { - queryable = queryable.Where(data => - query.Ids.Contains(data.Id)); - } + queryable = queryable.Where(data => query.Ids.Contains(data.Id)); if (!string.IsNullOrEmpty(query.UserId)) - { - queryable = queryable.Where(i => - i.StoreData != null && i.StoreData.UserStores.Any(u => u.ApplicationUserId == query.UserId)); - } + queryable = queryable.Where(data => + data.StoreData.UserStores.Any(u => u.ApplicationUserId == query.UserId)); queryable = queryable.OrderByDescending(u => u.Created); if (query.Skip.HasValue) - { queryable = queryable.Skip(query.Skip.Value); - } if (query.Count.HasValue) - { queryable = queryable.Take(query.Count.Value); - } + var items = await queryable.ToArrayAsync(cancellationToken); return items; } @@ -221,5 +226,6 @@ namespace BTCPayServer.Services.PaymentRequests public int? Skip { get; set; } public int? Count { get; set; } public string[] Ids { get; set; } + public string SearchText { get; set; } } } diff --git a/BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml b/BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml index 0c56a10a7..591236f24 100644 --- a/BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml +++ b/BTCPayServer/Views/UIPaymentRequest/EditPaymentRequest.cshtml @@ -1,6 +1,7 @@ @using BTCPayServer.Services.PaymentRequests @using System.Globalization @using BTCPayServer.Client +@using BTCPayServer.Controllers @using BTCPayServer.Forms @using BTCPayServer.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers @@ -82,6 +83,11 @@ +
+ + + +
@@ -99,7 +105,8 @@ "]" class="form-control" />
- @ViewLocalizer["This will send notification mails to the recipient, as configured by the {0}.", Html.ActionLink(StringLocalizer["email rules"], "StoreEmails", "UIStores", new { storeId = Model.StoreId })] + @ViewLocalizer["This will send notification mails to the recipient, as configured by the {0}.", + Html.ActionLink(StringLocalizer["email rules"], nameof(UIStoresController.StoreEmailRulesList), "UIStores", new { storeId = Model.StoreId })] @if (Model.HasEmailRules is not true) {