diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index e9e7bcffc..8ea206191 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -51,7 +51,7 @@ namespace BTCPayServer.Controllers StoreName = store.StoreName, StoreLink = Url.Action(nameof(StoresController.UpdateStore), "Stores", new { storeId = store.Id }), Id = invoice.Id, - Status = invoice.Status, + State = invoice.GetInvoiceState().ToString(), TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" : invoice.SpeedPolicy == SpeedPolicy.LowMediumSpeed ? "low-medium" : @@ -301,7 +301,9 @@ namespace BTCPayServer.Controllers throw new NotSupportedException(), TxCount = accounting.TxRequired, BtcPaid = accounting.Paid.ToString(), - Status = invoice.Status, +#pragma warning disable CS0618 // Type or member is obsolete + Status = invoice.StatusString, +#pragma warning restore CS0618 // Type or member is obsolete NetworkFee = paymentMethodDetails.GetTxFee(), IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1, ChangellyEnabled = changelly != null, @@ -373,7 +375,7 @@ namespace BTCPayServer.Controllers if (!HttpContext.WebSockets.IsWebSocketRequest) return NotFound(); var invoice = await _InvoiceRepository.GetInvoice(invoiceId); - if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired") + if (invoice == null || invoice.Status == InvoiceStatus.Complete || invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) return NotFound(); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); CompositeDisposable leases = new CompositeDisposable(); @@ -444,7 +446,7 @@ namespace BTCPayServer.Controllers model.Invoices.Add(new InvoiceModel() { Status = state.ToString(), - ShowCheckout = invoice.Status == "new", + ShowCheckout = invoice.Status == InvoiceStatus.New, Date = invoice.InvoiceTime, InvoiceId = invoice.Id, OrderId = invoice.OrderId ?? string.Empty, diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 0ad9f1b93..e7a0266eb 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -99,7 +99,7 @@ namespace BTCPayServer.Controllers if (!Uri.IsWellFormedUriString(entity.RedirectURL, UriKind.Absolute)) entity.RedirectURL = null; - entity.Status = "new"; + entity.Status = InvoiceStatus.New; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); HashSet currencyPairsToFetch = new HashSet(); diff --git a/BTCPayServer/Data/InvoiceData.cs b/BTCPayServer/Data/InvoiceData.cs index 65923631a..4914c9e7e 100644 --- a/BTCPayServer/Data/InvoiceData.cs +++ b/BTCPayServer/Data/InvoiceData.cs @@ -84,7 +84,7 @@ namespace BTCPayServer.Data public Services.Invoices.InvoiceState GetInvoiceState() { - return new Services.Invoices.InvoiceState() { Status = Status, ExceptionStatus = ExceptionStatus }; + return new Services.Invoices.InvoiceState(Status, ExceptionStatus); } } } diff --git a/BTCPayServer/Events/InvoiceDataChangedEvent.cs b/BTCPayServer/Events/InvoiceDataChangedEvent.cs index 77a9cf44a..3ef12e6a0 100644 --- a/BTCPayServer/Events/InvoiceDataChangedEvent.cs +++ b/BTCPayServer/Events/InvoiceDataChangedEvent.cs @@ -11,23 +11,14 @@ namespace BTCPayServer.Events public InvoiceDataChangedEvent(InvoiceEntity invoice) { InvoiceId = invoice.Id; - Status = invoice.Status; - ExceptionStatus = invoice.ExceptionStatus; + State = invoice.GetInvoiceState(); } - public string InvoiceId { get; set; } - public string Status { get; internal set; } - public string ExceptionStatus { get; internal set; } + public string InvoiceId { get; } + public InvoiceState State { get; } public override string ToString() { - if (string.IsNullOrEmpty(ExceptionStatus) || ExceptionStatus == "false") - { - return $"Invoice status is {Status}"; - } - else - { - return $"Invoice status is {Status} (Exception status: {ExceptionStatus})"; - } + return $"Invoice status is {State}"; } } } diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index 4abc0acca..c037a9724 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -61,14 +61,14 @@ namespace BTCPayServer.HostedServices private async Task UpdateInvoice(UpdateInvoiceContext context) { var invoice = context.Invoice; - if (invoice.Status == "new" && invoice.ExpirationTime < DateTimeOffset.UtcNow) + if (invoice.Status == InvoiceStatus.New && invoice.ExpirationTime < DateTimeOffset.UtcNow) { context.MarkDirty(); await _InvoiceRepository.UnaffectAddress(invoice.Id); context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1004, "invoice_expired")); - invoice.Status = "expired"; - if(invoice.ExceptionStatus == "paidPartial") + invoice.Status = InvoiceStatus.Expired; + if(invoice.ExceptionStatus == InvoiceExceptionStatus.PaidPartial) context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2000, "invoice_expiredPaidPartial")); } @@ -78,57 +78,57 @@ namespace BTCPayServer.HostedServices if (paymentMethod == null) return; var network = _NetworkProvider.GetNetwork(paymentMethod.GetId().CryptoCode); - if (invoice.Status == "new" || invoice.Status == "expired") + if (invoice.Status == InvoiceStatus.New || invoice.Status == InvoiceStatus.Expired) { if (accounting.Paid >= accounting.MinimumTotalDue) { - if (invoice.Status == "new") + if (invoice.Status == InvoiceStatus.New) { context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1003, "invoice_paidInFull")); - invoice.Status = "paid"; - invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null; + invoice.Status = InvoiceStatus.Paid; + invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? InvoiceExceptionStatus.PaidOver : InvoiceExceptionStatus.None; await _InvoiceRepository.UnaffectAddress(invoice.Id); context.MarkDirty(); } - else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate") + else if (invoice.Status == InvoiceStatus.Expired && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidLate) { - invoice.ExceptionStatus = "paidLate"; + invoice.ExceptionStatus = InvoiceExceptionStatus.PaidLate; context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1009, "invoice_paidAfterExpiration")); context.MarkDirty(); } } - if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != "paidPartial") + if (accounting.Paid < accounting.MinimumTotalDue && invoice.GetPayments().Count != 0 && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidPartial) { - invoice.ExceptionStatus = "paidPartial"; + invoice.ExceptionStatus = InvoiceExceptionStatus.PaidPartial; context.MarkDirty(); } } // Just make sure RBF did not cancelled a payment - if (invoice.Status == "paid") + if (invoice.Status == InvoiceStatus.Paid) { - if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == "paidOver") + if (accounting.MinimumTotalDue <= accounting.Paid && accounting.Paid <= accounting.TotalDue && invoice.ExceptionStatus == InvoiceExceptionStatus.PaidOver) { - invoice.ExceptionStatus = null; + invoice.ExceptionStatus = InvoiceExceptionStatus.None; context.MarkDirty(); } - if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != "paidOver") + if (accounting.Paid > accounting.TotalDue && invoice.ExceptionStatus != InvoiceExceptionStatus.PaidOver) { - invoice.ExceptionStatus = "paidOver"; + invoice.ExceptionStatus = InvoiceExceptionStatus.PaidOver; context.MarkDirty(); } if (accounting.Paid < accounting.MinimumTotalDue) { - invoice.Status = "new"; - invoice.ExceptionStatus = accounting.Paid == Money.Zero ? null : "paidPartial"; + invoice.Status = InvoiceStatus.New; + invoice.ExceptionStatus = accounting.Paid == Money.Zero ? InvoiceExceptionStatus.None : InvoiceExceptionStatus.PaidPartial; context.MarkDirty(); } } - if (invoice.Status == "paid") + if (invoice.Status == InvoiceStatus.Paid) { var confirmedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentConfirmed(p, invoice.SpeedPolicy, network)); @@ -140,25 +140,25 @@ namespace BTCPayServer.HostedServices { await _InvoiceRepository.UnaffectAddress(invoice.Id); context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1013, "invoice_failedToConfirm")); - invoice.Status = "invalid"; + invoice.Status = InvoiceStatus.Invalid; context.MarkDirty(); } else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue) { await _InvoiceRepository.UnaffectAddress(invoice.Id); context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1005, "invoice_confirmed")); - invoice.Status = "confirmed"; + invoice.Status = InvoiceStatus.Confirmed; context.MarkDirty(); } } - if (invoice.Status == "confirmed") + if (invoice.Status == InvoiceStatus.Confirmed) { var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network)); if (completedAccounting.Paid >= accounting.MinimumTotalDue) { context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1006, "invoice_completed")); - invoice.Status = "complete"; + invoice.Status = InvoiceStatus.Complete; context.MarkDirty(); } } @@ -290,7 +290,7 @@ namespace BTCPayServer.HostedServices await UpdateInvoice(updateContext); if (updateContext.Dirty) { - await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.Status, invoice.ExceptionStatus); + await _InvoiceRepository.UpdateInvoiceStatus(invoice.Id, invoice.GetInvoiceState()); updateContext.Events.Insert(0, new InvoiceDataChangedEvent(invoice)); } @@ -299,8 +299,8 @@ namespace BTCPayServer.HostedServices _EventAggregator.Publish(evt, evt.GetType()); } - if (invoice.Status == "complete" || - ((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) + if (invoice.Status == InvoiceStatus.Complete || + ((invoice.Status == InvoiceStatus.Invalid || invoice.Status == InvoiceStatus.Expired) && invoice.MonitoringExpiration < DateTimeOffset.UtcNow)) { if (await _InvoiceRepository.RemovePendingInvoice(invoice.Id)) _EventAggregator.Publish(new InvoiceStopWatchedEvent(invoice.Id)); diff --git a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs index 193d20157..be04b907b 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoiceDetailsModel.cs @@ -81,11 +81,11 @@ namespace BTCPayServer.Models.InvoicingModels public string BOLT11 { get; set; } } - public string Status + public string State { get; set; } - public string StatusException { get; set; } + public InvoiceExceptionStatus StatusException { get; set; } public DateTimeOffset CreatedDate { get; set; diff --git a/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs b/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs index dad7363fe..f71366ec1 100644 --- a/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs +++ b/BTCPayServer/Services/Invoices/Export/InvoiceExport.cs @@ -77,7 +77,9 @@ namespace BTCPayServer.Services.Invoices.Export CreatedDate = invoice.InvoiceTime.UtcDateTime, ExpirationDate = invoice.ExpirationTime.UtcDateTime, MonitoringDate = invoice.MonitoringExpiration.UtcDateTime, - Status = invoice.Status, +#pragma warning disable CS0618 // Type or member is obsolete + Status = invoice.StatusString, +#pragma warning restore CS0618 // Type or member is obsolete ItemCode = invoice.ProductInformation?.ItemCode, ItemDesc = invoice.ProductInformation?.ItemDesc, FiatPrice = invoice.ProductInformation?.Price ?? 0, diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index b37bf0868..e223f8690 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -226,15 +226,23 @@ namespace BTCPayServer.Services.Invoices #pragma warning restore CS0618 } - public string Status + [JsonIgnore] + public InvoiceStatus Status { get; set; } - public string ExceptionStatus + [JsonProperty(PropertyName = "status")] + [Obsolete("Use Status instead")] + public string StatusString => InvoiceState.ToString(Status); + [JsonIgnore] + public InvoiceExceptionStatus ExceptionStatus { get; set; } + [JsonProperty(PropertyName = "exceptionStatus")] + [Obsolete("Use ExceptionStatus instead")] + public string ExceptionStatusString => InvoiceState.ToString(ExceptionStatus); [Obsolete("Use GetPayments instead")] public List Payments @@ -341,7 +349,10 @@ namespace BTCPayServer.Services.Invoices CurrentTime = DateTimeOffset.UtcNow, InvoiceTime = InvoiceTime, ExpirationTime = ExpirationTime, - Status = Status, +#pragma warning disable CS0618 // Type or member is obsolete + Status = StatusString, + ExceptionStatus = ExceptionStatus == InvoiceExceptionStatus.None ? new JValue(false) : new JValue(ExceptionStatusString), +#pragma warning restore CS0618 // Type or member is obsolete Currency = ProductInformation.Currency, Flags = new Flags() { Refundable = Refundable }, PaymentSubtotals = new Dictionary(), @@ -447,7 +458,6 @@ namespace BTCPayServer.Services.Invoices dto.Token = Encoders.Base58.EncodeData(RandomUtils.GetBytes(16)); //No idea what it is useful for dto.Guid = Guid.NewGuid().ToString(); - dto.ExceptionStatus = ExceptionStatus == null ? new JValue(false) : new JValue(ExceptionStatus); return dto; } @@ -529,38 +539,104 @@ namespace BTCPayServer.Services.Invoices public InvoiceState GetInvoiceState() { - return new InvoiceState() { Status = Status, ExceptionStatus = ExceptionStatus }; + return new InvoiceState(Status, ExceptionStatus); } } + public enum InvoiceStatus + { + New, + Paid, + Expired, + Invalid, + Complete, + Confirmed + } + public enum InvoiceExceptionStatus + { + None, + PaidLate, + PaidPartial, + Marked, + Invalid, + PaidOver + } public class InvoiceState { - public string Status { get; set; } - public string ExceptionStatus { get; set; } + static Dictionary _StringToInvoiceStatus; + static Dictionary _InvoiceStatusToString; + + static Dictionary _StringToExceptionStatus; + static Dictionary _ExceptionStatusToString; + + static InvoiceState() + { + _StringToInvoiceStatus = new Dictionary(); + _StringToInvoiceStatus.Add("paid", InvoiceStatus.Paid); + _StringToInvoiceStatus.Add("expired", InvoiceStatus.Expired); + _StringToInvoiceStatus.Add("invalid", InvoiceStatus.Invalid); + _StringToInvoiceStatus.Add("complete", InvoiceStatus.Complete); + _StringToInvoiceStatus.Add("new", InvoiceStatus.New); + _StringToInvoiceStatus.Add("confirmed", InvoiceStatus.Confirmed); + _InvoiceStatusToString = _StringToInvoiceStatus.ToDictionary(kv => kv.Value, kv => kv.Key); + + _StringToExceptionStatus = new Dictionary(); + _StringToExceptionStatus.Add(string.Empty, InvoiceExceptionStatus.None); + _StringToExceptionStatus.Add("paidPartial", InvoiceExceptionStatus.PaidPartial); + _StringToExceptionStatus.Add("paidLate", InvoiceExceptionStatus.PaidLate); + _StringToExceptionStatus.Add("paidOver", InvoiceExceptionStatus.PaidOver); + _StringToExceptionStatus.Add("marked", InvoiceExceptionStatus.Marked); + _ExceptionStatusToString = _StringToExceptionStatus.ToDictionary(kv => kv.Value, kv => kv.Key); + _StringToExceptionStatus.Add("false", InvoiceExceptionStatus.None); + } + public InvoiceState(string status, string exceptionStatus) + { + Status = _StringToInvoiceStatus[status]; + ExceptionStatus = _StringToExceptionStatus[exceptionStatus ?? string.Empty]; + } + public InvoiceState(InvoiceStatus status, InvoiceExceptionStatus exceptionStatus) + { + Status = status; + ExceptionStatus = exceptionStatus; + } + + public InvoiceStatus Status { get; } + public InvoiceExceptionStatus ExceptionStatus { get; } + + public static string ToString(InvoiceStatus status) + { + return _InvoiceStatusToString[status]; + } + + public static string ToString(InvoiceExceptionStatus exceptionStatus) + { + return _ExceptionStatusToString[exceptionStatus]; + } + public bool CanMarkComplete() { - return (Status == "paid") || + return (Status == InvoiceStatus.Paid) || #pragma warning disable CA1305 // Specify IFormatProvider - ((Status == "new" || Status == "expired") && ExceptionStatus?.ToString() == "paidPartial") || - ((Status == "new" || Status == "expired") && ExceptionStatus?.ToString() == "paidLate") || - (Status != "complete" && ExceptionStatus?.ToString() == "marked") || - (Status == "invalid"); + ((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) || + ((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) || + (Status != InvoiceStatus.Complete && ExceptionStatus == InvoiceExceptionStatus.Marked) || + (Status == InvoiceStatus.Invalid); #pragma warning restore CA1305 // Specify IFormatProvider } public bool CanMarkInvalid() { - return (Status == "paid") || - (Status == "new") || + return (Status == InvoiceStatus.Paid) || + (Status == InvoiceStatus.New) || #pragma warning disable CA1305 // Specify IFormatProvider - ((Status == "new" || Status == "expired") && ExceptionStatus?.ToString() == "paidPartial") || - ((Status == "new" || Status == "expired") && ExceptionStatus?.ToString() == "paidLate") || - (Status != "invalid" && ExceptionStatus?.ToString() == "marked"); + ((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidPartial) || + ((Status == InvoiceStatus.New || Status == InvoiceStatus.Expired) && ExceptionStatus == InvoiceExceptionStatus.PaidLate) || + (Status != InvoiceStatus.Invalid && ExceptionStatus == InvoiceExceptionStatus.Marked); #pragma warning restore CA1305 // Specify IFormatProvider; } public override string ToString() { - return Status + (ExceptionStatus == null ? string.Empty : $" ({ExceptionStatus})"); + return ToString(Status) + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})"); } } diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 0a846f10d..5bbaccb6e 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -126,7 +126,9 @@ namespace BTCPayServer.Services.Invoices Created = invoice.InvoiceTime, Blob = ToBytes(invoice, null), OrderId = invoice.OrderId, - Status = invoice.Status, +#pragma warning disable CS0618 // Type or member is obsolete + Status = invoice.StatusString, +#pragma warning restore CS0618 // Type or member is obsolete ItemCode = invoice.ProductInformation.ItemCode, CustomerEmail = invoice.RefundMail }); @@ -316,15 +318,15 @@ namespace BTCPayServer.Services.Invoices }); } - public async Task UpdateInvoiceStatus(string invoiceId, string status, string exceptionStatus) + public async Task UpdateInvoiceStatus(string invoiceId, InvoiceState invoiceState) { using (var context = _ContextFactory.CreateContext()) { var invoiceData = await context.FindAsync(invoiceId).ConfigureAwait(false); if (invoiceData == null) return; - invoiceData.Status = status; - invoiceData.ExceptionStatus = exceptionStatus; + invoiceData.Status = InvoiceState.ToString(invoiceState.Status); + invoiceData.ExceptionStatus = InvoiceState.ToString(invoiceState.ExceptionStatus); await context.SaveChangesAsync().ConfigureAwait(false); } } @@ -385,8 +387,9 @@ namespace BTCPayServer.Services.Invoices return paymentEntity; }).ToList(); #pragma warning restore CS0618 - entity.ExceptionStatus = invoice.ExceptionStatus; - entity.Status = invoice.Status; + var state = invoice.GetInvoiceState(); + entity.ExceptionStatus = state.ExceptionStatus; + entity.Status = state.Status; entity.RefundMail = invoice.CustomerEmail; entity.Refundable = invoice.RefundAddresses.Count != 0; if (invoice.HistoricalAddressInvoices != null) diff --git a/BTCPayServer/Views/Invoice/Invoice.cshtml b/BTCPayServer/Views/Invoice/Invoice.cshtml index 83310696c..848216821 100644 --- a/BTCPayServer/Views/Invoice/Invoice.cshtml +++ b/BTCPayServer/Views/Invoice/Invoice.cshtml @@ -54,6 +54,10 @@ Id @Model.Id + + State + @Model.State + Created date @Model.CreatedDate.ToBrowserDate() @@ -70,14 +74,6 @@ Transaction speed @Model.TransactionSpeed - - Status - @Model.Status - - - Status Exception - @Model.StatusException - Refund email @Model.RefundEmail @@ -221,7 +217,7 @@ Rate Paid Due - @if (Model.StatusException == "paidOver") + @if (Model.StatusException == InvoiceExceptionStatus.PaidOver) { Overpaid } @@ -236,7 +232,7 @@ @payment.Rate @payment.Paid @payment.Due - @if (Model.StatusException == "paidOver") + @if (Model.StatusException == InvoiceExceptionStatus.PaidOver) { @payment.Overpaid } diff --git a/BTCPayServer/Views/Invoice/_ViewImports.cshtml b/BTCPayServer/Views/Invoice/_ViewImports.cshtml new file mode 100644 index 000000000..fbea1e3d7 --- /dev/null +++ b/BTCPayServer/Views/Invoice/_ViewImports.cshtml @@ -0,0 +1 @@ +@using BTCPayServer.Services.Invoices