diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 97799073c..330a71d06 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -48,6 +48,7 @@ using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Security; using NBXplorer.Models; using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; +using NBitpayClient.Extensions; namespace BTCPayServer.Tests { @@ -350,57 +351,6 @@ namespace BTCPayServer.Tests } } - [Fact] - [Trait("Integration", "Integration")] - public void CanPayUsingBIP70() - { - using (var tester = ServerTester.Create()) - { - tester.Start(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - Assert.True(user.BitPay.TestAccess(Facade.Merchant)); - var invoice = user.BitPay.CreateInvoice(new Invoice() - { - Buyer = new Buyer() { email = "test@fwf.com" }, - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - //RedirectURL = redirect + "redirect", - //NotificationURL = CallbackUri + "/notification", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - Assert.False(invoice.Refundable); - - var url = new BitcoinUrlBuilder(invoice.PaymentUrls.BIP72); - var request = url.GetPaymentRequest(); - var payment = request.CreatePayment(); - - Transaction tx = new Transaction(); - tx.Outputs.AddRange(request.Details.Outputs.Select(o => new TxOut(o.Amount, o.Script))); - var cashCow = tester.ExplorerNode; - tx = cashCow.FundRawTransaction(tx).Transaction; - tx = cashCow.SignRawTransaction(tx); - - payment.Transactions.Add(tx); - - payment.RefundTo.Add(new PaymentOutput(Money.Coins(1.0m), new Key().ScriptPubKey)); - var ack = payment.SubmitPayment(); - Assert.NotNull(ack); - - Eventually(() => - { - var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant); - Assert.Equal("paid", localInvoice.Status); - Assert.True(localInvoice.Refundable); - }); - } - } - [Fact] [Trait("Integration", "Integration")] public async Task CanSetLightningServer() @@ -934,6 +884,17 @@ namespace BTCPayServer.Tests var result = client.SendAsync(message).GetAwaiter().GetResult(); result.EnsureSuccessStatusCode(); ///////////////////// + + // Have error 403 with bad signature + client = new HttpClient(); + HttpRequestMessage mess = new HttpRequestMessage(HttpMethod.Get, tester.PayTester.ServerUri.AbsoluteUri + "tokens"); + mess.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json"); + mess.Headers.Add("x-signature", "3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe"); + mess.Headers.Add("x-identity", "04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99"); + mess.Method = HttpMethod.Get; + result = client.SendAsync(mess).GetAwaiter().GetResult(); + Assert.Equal(System.Net.HttpStatusCode.Unauthorized, result.StatusCode); + // } } @@ -1801,7 +1762,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate Assert.True(IsMapped(invoice, ctx)); cashCow.SendToAddress(invoiceAddress, firstPayment); - var invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); + var invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult(); Assert.Single(invoiceEntity.HistoricalAddresses); Assert.Null(invoiceEntity.HistoricalAddresses[0].UnAssigned); @@ -1819,7 +1780,7 @@ ReceivedDate,StoreId,OrderId,InvoiceId,CreatedDate,ExpirationDate,MonitoringDate Assert.True(IsMapped(invoice, ctx)); Assert.True(IsMapped(localInvoice, ctx)); - invoiceEntity = repo.GetInvoice(null, invoice.Id, true).GetAwaiter().GetResult(); + invoiceEntity = repo.GetInvoice(invoice.Id, true).GetAwaiter().GetResult(); var historical1 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == invoice.BitcoinAddress); Assert.NotNull(historical1.UnAssigned); var historical2 = invoiceEntity.HistoricalAddresses.FirstOrDefault(h => h.GetAddress() == localInvoice.BitcoinAddress); diff --git a/BTCPayServer/Authentication/TokenRepository.cs b/BTCPayServer/Authentication/TokenRepository.cs index 776533fe3..c1f3b5e12 100644 --- a/BTCPayServer/Authentication/TokenRepository.cs +++ b/BTCPayServer/Authentication/TokenRepository.cs @@ -242,6 +242,8 @@ namespace BTCPayServer.Authentication using (var ctx = _Factory.CreateContext()) { var token = await ctx.PairedSINData.FindAsync(tokenId); + if (token == null) + return null; return CreateTokenEntity(token); } } diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index e883fb649..12834d418 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.3.23 + 1.0.3.27 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -33,7 +33,7 @@ - + @@ -46,7 +46,7 @@ all runtime; build; native; contentfiles; analyzers - + @@ -136,9 +136,6 @@ $(IncludeRazorContentInPack) - - $(IncludeRazorContentInPack) - $(IncludeRazorContentInPack) @@ -154,7 +151,7 @@ $(IncludeRazorContentInPack) - + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index e477a200b..e0272d854 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -141,6 +141,18 @@ namespace BTCPayServer.Configuration Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray())); + var services = conf.GetOrDefault("externalservices", null); + if(services != null) + { + foreach(var service in services.Split(new[] { ';', ',' }) + .Select(p => p.Split(':')) + .Where(p => p.Length == 2) + .Select(p => (Name: p[0], Link: p[1]))) + { + ExternalServices.AddOrReplace(service.Name, service.Link); + } + } + PostgresConnectionString = conf.GetOrDefault("postgres", null); MySQLConnectionString = conf.GetOrDefault("mysql", null); BundleJsCss = conf.GetOrDefault("bundlejscss", true); @@ -248,6 +260,8 @@ namespace BTCPayServer.Configuration public string RootPath { get; set; } public Dictionary InternalLightningByCryptoCode { get; set; } = new Dictionary(); + public Dictionary ExternalServices { get; set; } = new Dictionary(); + public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices(); public BTCPayNetworkProvider NetworkProvider { get; set; } diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index ef8511db3..1f26bd61c 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -33,6 +33,7 @@ namespace BTCPayServer.Configuration app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue); app.Option("--mysql", $"Connection string to a MySQL database (default: SQLite)", CommandOptionType.SingleValue); app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue); + app.Option("--externalservices", $"Links added to external services inside Server Settings / Services under the format service1:path2;service2:path2.(default: empty)", CommandOptionType.SingleValue); app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue); app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue); app.Option("--sshconnection", "SSH server to manage BTCPay under the form user@server:port (default: root@externalhost or empty)", CommandOptionType.SingleValue); diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index 1f855ad05..c8ea7b594 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using BTCPayServer.Data; +using BTCPayServer.Filters; using BTCPayServer.Models.AppViewModels; using BTCPayServer.Security; using BTCPayServer.Services.Apps; @@ -32,6 +33,7 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("/apps/{appId}/pos")] + [XFrameOptionsAttribute(null)] public async Task ViewPointOfSale(string appId) { var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale); diff --git a/BTCPayServer/Controllers/HomeController.cs b/BTCPayServer/Controllers/HomeController.cs index 6415f13e7..6747570ff 100644 --- a/BTCPayServer/Controllers/HomeController.cs +++ b/BTCPayServer/Controllers/HomeController.cs @@ -50,6 +50,7 @@ namespace BTCPayServer.Controllers try { BitcoinUrlBuilder urlBuilder = new BitcoinUrlBuilder(vm.BitpayLink); +#pragma warning disable CS0618 // Type or member is obsolete if (!urlBuilder.PaymentRequestUrl.DnsSafeHost.EndsWith("bitpay.com", StringComparison.OrdinalIgnoreCase)) { throw new Exception("This tool only work with bitpay"); @@ -57,6 +58,7 @@ namespace BTCPayServer.Controllers var client = HttpClientFactory.CreateClient(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, urlBuilder.PaymentRequestUrl); +#pragma warning restore CS0618 // Type or member is obsolete request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/payment-request")); var result = await client.SendAsync(request); // {"network":"main","currency":"BTC","requiredFeeRate":29.834,"outputs":[{"amount":255900,"address":"1PgPo5d4swD6pKfCgoXtoW61zqTfX9H7tj"}],"time":"2018-12-03T14:39:47.162Z","expires":"2018-12-03T14:54:47.162Z","memo":"Payment request for BitPay invoice HHfG8cprRMzZG6MErCqbjv for merchant VULTR Holdings LLC","paymentUrl":"https://bitpay.com/i/HHfG8cprRMzZG6MErCqbjv","paymentId":"HHfG8cprRMzZG6MErCqbjv"} diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index da7bc8524..58c637a9d 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -40,16 +40,18 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("invoices/{id}")] - [AllowAnonymous] - public async Task> GetInvoice(string id, string token) + public async Task> GetInvoice(string id) { - var invoice = await _InvoiceRepository.GetInvoice(null, id); + var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery() + { + InvoiceId = id, + StoreId = new[] { HttpContext.GetStoreData().Id } + })).FirstOrDefault(); if (invoice == null) throw new BitpayHttpException(404, "Object not found"); var resp = invoice.EntityToDTO(_NetworkProvider); return new DataWrapper(resp); } - [HttpGet] [Route("invoices")] public async Task> GetInvoices( diff --git a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs b/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs deleted file mode 100644 index 3ba0a4120..000000000 --- a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using BTCPayServer.Filters; -using BTCPayServer.Logging; -using BTCPayServer.Payments; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using NBitcoin; -using NBitcoin.Payment; - -namespace BTCPayServer.Controllers -{ - public partial class InvoiceController - { - [HttpGet] - [Route("i/{invoiceId}/{cryptoCode?}")] - [AcceptMediaTypeConstraint("application/bitcoin-paymentrequest")] - public async Task GetInvoiceRequest(string invoiceId, string cryptoCode = null) - { - if (cryptoCode == null) - cryptoCode = "BTC"; - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); - var network = _NetworkProvider.GetNetwork(cryptoCode); - var paymentMethodId = new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike); - if (invoice == null || invoice.IsExpired() || network == null || !invoice.Support(paymentMethodId)) - return NotFound(); - - var dto = invoice.EntityToDTO(_NetworkProvider); - var paymentMethod = dto.CryptoInfo.First(c => c.GetpaymentMethodId() == paymentMethodId); - PaymentRequest request = new PaymentRequest - { - DetailsVersion = 1 - }; - request.Details.Expires = invoice.ExpirationTime; - request.Details.Memo = invoice.ProductInformation.ItemDesc; - request.Details.Network = network.NBitcoinNetwork; - request.Details.Outputs.Add(new PaymentOutput() { Amount = paymentMethod.Due, Script = BitcoinAddress.Create(paymentMethod.Address, network.NBitcoinNetwork).ScriptPubKey }); - request.Details.MerchantData = Encoding.UTF8.GetBytes(invoice.Id); - request.Details.Time = DateTimeOffset.UtcNow; - request.Details.PaymentUrl = new Uri(invoice.ServerUrl.WithTrailingSlash() + ($"i/{invoice.Id}"), UriKind.Absolute); - - var store = await _StoreRepository.FindStore(invoice.StoreId); - if (store == null) - throw new BitpayHttpException(401, "Unknown store"); - - if (store.StoreCertificate != null) - { - try - { - request.Sign(store.StoreCertificate, PKIType.X509SHA256); - } - catch (Exception ex) - { - Logs.PayServer.LogWarning(ex, "Error while signing payment request"); - } - } - - return new PaymentRequestActionResult(request); - } - - [HttpPost] - [Route("i/{invoiceId}", Order = 99)] - [Route("i/{invoiceId}/{cryptoCode}", Order = 99)] - [MediaTypeConstraint("application/bitcoin-payment")] - public async Task PostPayment(string invoiceId, string cryptoCode = null) - { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); - if (cryptoCode == null) - cryptoCode = "BTC"; - var network = _NetworkProvider.GetNetwork(cryptoCode); - if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(new PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike))) - return NotFound(); - - var wallet = _WalletProvider.GetWallet(network); - if (wallet == null) - return NotFound(); - var payment = PaymentMessage.Load(Request.Body, network.NBitcoinNetwork); - var unused = wallet.BroadcastTransactionsAsync(payment.Transactions); - await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork); - return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase...")); - } - } - - - public class PaymentRequestActionResult : IActionResult - { - PaymentRequest req; - public PaymentRequestActionResult(PaymentRequest req) - { - this.req = req; - } - public Task ExecuteResultAsync(ActionContext context) - { - context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; - context.HttpContext.Response.ContentType = "application/bitcoin-paymentrequest"; - req.WriteTo(context.HttpContext.Response.Body); - return Task.CompletedTask; - } - } - public class PaymentAckActionResult : IActionResult - { - PaymentACK req; - public PaymentAckActionResult(PaymentACK req) - { - this.req = req; - } - public Task ExecuteResultAsync(ActionContext context) - { - context.HttpContext.Response.Headers["Content-Transfer-Encoding"] = "binary"; - context.HttpContext.Response.ContentType = "application/bitcoin-paymentack"; - req.WriteTo(context.HttpContext.Response.Body); - return Task.CompletedTask; - } - } -} diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index fdc983f87..e9e7bcffc 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Filters; +using BTCPayServer.Models; using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Payments; using BTCPayServer.Payments.Changelly; @@ -30,11 +31,13 @@ namespace BTCPayServer.Controllers { [HttpGet] [Route("invoices/{invoiceId}")] + [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] public async Task Invoice(string invoiceId) { var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery() { InvoiceId = invoiceId, + UserId = GetUserId(), IncludeAddresses = true, IncludeEvents = true })).FirstOrDefault(); @@ -209,7 +212,7 @@ namespace BTCPayServer.Controllers private async Task GetInvoiceModel(string invoiceId, string paymentMethodIdStr) { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId); if (invoice == null) return null; var store = await _StoreRepository.FindStore(invoice.StoreId); @@ -369,7 +372,7 @@ namespace BTCPayServer.Controllers { if (!HttpContext.WebSockets.IsWebSocketRequest) return NotFound(); - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId); if (invoice == null || invoice.Status == "complete" || invoice.Status == "invalid" || invoice.Status == "expired") return NotFound(); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); @@ -437,15 +440,18 @@ namespace BTCPayServer.Controllers var list = await ListInvoicesProcess(searchTerm, skip, count); foreach (var invoice in list) { + var state = invoice.GetInvoiceState(); model.Invoices.Add(new InvoiceModel() { - Status = invoice.Status + (invoice.ExceptionStatus == null ? string.Empty : $" ({invoice.ExceptionStatus})"), + Status = state.ToString(), ShowCheckout = invoice.Status == "new", Date = invoice.InvoiceTime, InvoiceId = invoice.Id, OrderId = invoice.OrderId ?? string.Empty, RedirectUrl = invoice.RedirectURL ?? string.Empty, - AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}" + AmountCurrency = $"{invoice.ProductInformation.Price.ToString(CultureInfo.InvariantCulture)} {invoice.ProductInformation.Currency}", + CanMarkInvalid = state.CanMarkInvalid(), + CanMarkComplete = state.CanMarkComplete() }); } return View(model); @@ -585,17 +591,60 @@ namespace BTCPayServer.Controllers }); } - [HttpPost] - [Route("invoices/invalidatepaid")] + [HttpGet] + [Route("invoices/{invoiceId}/changestate/{newState}")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] [BitpayAPIConstraint(false)] - public async Task InvalidatePaidInvoice(string invoiceId) + public IActionResult ChangeInvoiceState(string invoiceId, string newState) { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + if (newState == "invalid") + { + return View("Confirm", new ConfirmModel() + { + Action = "Make invoice invalid", + Title = "Change invoice state", + Description = $"You will transition the state of this invoice to \"invalid\", do you want to continue?", + }); + } + else if (newState == "complete") + { + return View("Confirm", new ConfirmModel() + { + Action = "Make invoice complete", + Title = "Change invoice state", + Description = $"You will transition the state of this invoice to \"complete\", do you want to continue?", + ButtonClass = "btn-primary" + }); + } + else + return NotFound(); + } + + [HttpPost] + [Route("invoices/{invoiceId}/changestate/{newState}")] + [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [BitpayAPIConstraint(false)] + public async Task ChangeInvoiceStateConfirm(string invoiceId, string newState) + { + var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery() + { + InvoiceId = invoiceId, + UserId = GetUserId() + })).FirstOrDefault(); if (invoice == null) return NotFound(); - await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId); - _EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid")); + if (newState == "invalid") + { + await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId); + _EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid")); + StatusMessage = "Invoice marked invalid"; + } + else if(newState == "complete") + { + await _InvoiceRepository.UpdatePaidInvoiceToComplete(invoiceId); + _EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2008, "invoice_markedComplete")); + StatusMessage = "Invoice marked complete"; + } return RedirectToAction(nameof(ListInvoices)); } diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index e44ae8f43..c6b2df2d7 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -435,12 +435,27 @@ namespace BTCPayServer.Controllers }); } } - result.HasSSH = _Options.SSHSettings != null; + foreach(var externalService in _Options.ExternalServices) + { + result.ExternalServices.Add(new ServicesViewModel.ExternalService() + { + Name = externalService.Key, + Link = this.Request.GetRelativePath(externalService.Value) + }); + } + if(_Options.SSHSettings != null) + { + result.ExternalServices.Add(new ServicesViewModel.ExternalService() + { + Name = "SSH", + Link = this.Url.Action(nameof(SSHService)) + }); + } return View(result); } - [Route("server/services/lnd-grpc/{cryptoCode}/{index}")] - public IActionResult LndGrpcServices(string cryptoCode, int index, uint? nonce) + [Route("server/services/lnd/{cryptoCode}/{index}")] + public IActionResult LndServices(string cryptoCode, int index, uint? nonce) { if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud)) { @@ -451,9 +466,18 @@ namespace BTCPayServer.Controllers if (external == null) return NotFound(); var model = new LndGrpcServicesViewModel(); + if (external.ConnectionType == LightningConnectionType.LndGRPC) + { + model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}"; + model.SSL = external.BaseUri.Scheme == "https"; + model.ConnectionType = "GRPC"; + } + else if(external.ConnectionType == LightningConnectionType.LndREST) + { + model.Uri = external.BaseUri.AbsoluteUri; + model.ConnectionType = "REST"; + } - model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}"; - model.SSL = external.BaseUri.Scheme == "https"; if (external.CertificateThumbprint != null) { model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint); @@ -462,10 +486,14 @@ namespace BTCPayServer.Controllers { model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon); } + if (external.RestrictedMacaroon != null) + { + model.RestrictedMacaroon = Encoders.Hex.EncodeData(external.RestrictedMacaroon); + } if (nonce != null) { - var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce.Value); + var configKey = GetConfigKey("lnd", cryptoCode, index, nonce.Value); var lnConfig = _LnConfigProvider.GetConfig(configKey); if (lnConfig != null) { @@ -492,29 +520,44 @@ namespace BTCPayServer.Controllers return Json(conf); } - [Route("server/services/lnd-grpc/{cryptoCode}/{index}")] + [Route("server/services/lnd/{cryptoCode}/{index}")] [HttpPost] - public IActionResult LndGrpcServicesPost(string cryptoCode, int index) + public IActionResult LndServicesPost(string cryptoCode, int index) { var external = GetExternalLndConnectionString(cryptoCode, index); if (external == null) return NotFound(); LightningConfigurations confs = new LightningConfigurations(); - LightningConfiguration conf = new LightningConfiguration(); - conf.Type = "grpc"; - conf.ChainType = _Options.NetworkType.ToString(); - conf.CryptoCode = cryptoCode; - conf.Host = external.BaseUri.DnsSafeHost; - conf.Port = external.BaseUri.Port; - conf.SSL = external.BaseUri.Scheme == "https"; - conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon); - conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint); - confs.Configurations.Add(conf); - + if (external.ConnectionType == LightningConnectionType.LndGRPC) + { + LightningConfiguration conf = new LightningConfiguration(); + conf.Type = "grpc"; + conf.ChainType = _Options.NetworkType.ToString(); + conf.CryptoCode = cryptoCode; + conf.Host = external.BaseUri.DnsSafeHost; + conf.Port = external.BaseUri.Port; + conf.SSL = external.BaseUri.Scheme == "https"; + conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon); + conf.RestrictedMacaroon = external.RestrictedMacaroon == null ? null : Encoders.Hex.EncodeData(external.RestrictedMacaroon); + conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint); + confs.Configurations.Add(conf); + } + else if (external.ConnectionType == LightningConnectionType.LndREST) + { + var restconf = new LNDRestConfiguration(); + restconf.Type = "lnd-rest"; + restconf.ChainType = _Options.NetworkType.ToString(); + restconf.CryptoCode = cryptoCode; + restconf.Uri = external.BaseUri.AbsoluteUri; + restconf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon); + restconf.RestrictedMacaroon = external.RestrictedMacaroon == null ? null : Encoders.Hex.EncodeData(external.RestrictedMacaroon); + restconf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint); + confs.Configurations.Add(restconf); + } var nonce = RandomUtils.GetUInt32(); - var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce); + var configKey = GetConfigKey("lnd", cryptoCode, index, nonce); _LnConfigProvider.KeepConfig(configKey, confs); - return RedirectToAction(nameof(LndGrpcServices), new { cryptoCode = cryptoCode, nonce = nonce }); + return RedirectToAction(nameof(LndServices), new { cryptoCode = cryptoCode, nonce = nonce }); } private LightningConnectionString GetExternalLndConnectionString(string cryptoCode, int index) @@ -536,29 +579,19 @@ namespace BTCPayServer.Controllers return null; } } - return connectionString; - } - - [Route("server/services/lnd-rest/{cryptoCode}/{index}")] - public IActionResult LndRestServices(string cryptoCode, int index, uint? nonce) - { - if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud)) + if (connectionString.RestrictedMacaroonFilePath != null) { - StatusMessage = $"Error: {cryptoCode} is not fully synched"; - return RedirectToAction(nameof(Services)); + try + { + connectionString.RestrictedMacaroon = System.IO.File.ReadAllBytes(connectionString.RestrictedMacaroonFilePath); + } + catch + { + Logs.Configuration.LogWarning($"{cryptoCode}: The restrictedmacaroon file path of the external LND grpc config was not found ({connectionString.RestrictedMacaroonFilePath})"); + } + connectionString.RestrictedMacaroonFilePath = null; } - var external = GetExternalLndConnectionString(cryptoCode, index); - if (external == null) - return NotFound(); - var model = new LndRestServicesViewModel(); - - model.BaseApiUrl = external.BaseUri.ToString(); - if (external.CertificateThumbprint != null) - model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint); - if (external.Macaroon != null) - model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon); - - return View(model); + return connectionString; } [Route("server/services/ssh")] diff --git a/BTCPayServer/Data/InvoiceData.cs b/BTCPayServer/Data/InvoiceData.cs index 10d81c26a..65923631a 100644 --- a/BTCPayServer/Data/InvoiceData.cs +++ b/BTCPayServer/Data/InvoiceData.cs @@ -81,5 +81,10 @@ namespace BTCPayServer.Data get; set; } public List PendingInvoices { get; set; } + + public Services.Invoices.InvoiceState GetInvoiceState() + { + return new Services.Invoices.InvoiceState() { Status = Status, ExceptionStatus = ExceptionStatus }; + } } } diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 8e7b694e4..b9f50431b 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -168,6 +168,15 @@ namespace BTCPayServer request.Path.ToUriComponent()); } + public static string GetRelativePath(this HttpRequest request, string path) + { + if (path.Length > 0 && path[0] != '/') + path = $"/{path}"; + return string.Concat( + request.PathBase.ToUriComponent(), + path); + } + public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl) { bool isRelative = diff --git a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs index 019a6c38b..455050373 100644 --- a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs +++ b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs @@ -330,7 +330,7 @@ namespace BTCPayServer.HostedServices { leases.Add(_EventAggregator.Subscribe(async e => { - var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id); + var invoice = await _InvoiceRepository.GetInvoice(e.Invoice.Id); if (invoice == null) return; List tasks = new List(); @@ -345,6 +345,7 @@ namespace BTCPayServer.HostedServices e.Name == "invoice_paidInFull" || e.Name == "invoice_failedToConfirm" || e.Name == "invoice_markedInvalid" || + e.Name == "invoice_markedComplete" || e.Name == "invoice_failedToConfirm" || e.Name == "invoice_completed" || e.Name == "invoice_expiredPaidPartial" diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index ddddd8698..4abc0acca 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -208,7 +208,7 @@ namespace BTCPayServer.HostedServices private async Task Wait(string invoiceId) { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId); try { var delay = invoice.ExpirationTime - DateTimeOffset.UtcNow; @@ -283,7 +283,7 @@ namespace BTCPayServer.HostedServices loopCount++; try { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true); if (invoice == null) break; var updateContext = new UpdateInvoiceContext(invoice); diff --git a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs index 5dfdd5260..7a4e3fb9c 100644 --- a/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs +++ b/BTCPayServer/Models/InvoicingModels/InvoicesModel.cs @@ -46,6 +46,9 @@ namespace BTCPayServer.Models.InvoicingModels { get; set; } + public bool CanMarkComplete { get; set; } + public bool CanMarkInvalid { get; set; } + public bool CanMarkStatus => CanMarkComplete || CanMarkInvalid; public bool ShowCheckout { get; set; } public string ExceptionStatus { get; set; } public string AmountCurrency diff --git a/BTCPayServer/Models/ServerViewModels/LndGrpcServicesViewModel.cs b/BTCPayServer/Models/ServerViewModels/LndGrpcServicesViewModel.cs index 1636b94f1..33bffd72b 100644 --- a/BTCPayServer/Models/ServerViewModels/LndGrpcServicesViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/LndGrpcServicesViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -10,8 +11,12 @@ namespace BTCPayServer.Models.ServerViewModels public string Host { get; set; } public bool SSL { get; set; } public string Macaroon { get; set; } + public string RestrictedMacaroon { get; set; } public string CertificateThumbprint { get; set; } public string QRCode { get; set; } public string QRCodeLink { get; set; } + [Display(Name = "REST Uri")] + public string Uri { get; set; } + public string ConnectionType { get; internal set; } } } diff --git a/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs index e103bd6bf..e3ddab4ea 100644 --- a/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs +++ b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs @@ -15,7 +15,13 @@ namespace BTCPayServer.Models.ServerViewModels public int Index { get; set; } } + public class ExternalService + { + public string Name { get; set; } + public string Link { get; set; } + } + public List LNDServices { get; set; } = new List(); - public bool HasSSH { get; set; } + public List ExternalServices { get; set; } = new List(); } } diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index 7ae9d6880..ca0860629 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -205,7 +205,7 @@ namespace BTCPayServer.Payments.Bitcoin async Task UpdatePaymentStates(BTCPayWallet wallet, string invoiceId) { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId, false); if (invoice == null) return null; List updatedPaymentEntities = new List(); @@ -315,7 +315,7 @@ namespace BTCPayServer.Payments.Bitcoin var invoices = await _InvoiceRepository.GetPendingInvoices(); foreach (var invoiceId in invoices) { - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId, true); if (invoice == null) continue; var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet(); diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index dcfa57541..1fef6f9c6 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -73,7 +73,7 @@ namespace BTCPayServer.Payments.Lightning { if (Listening(invoiceId)) return; - var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + var invoice = await _InvoiceRepository.GetInvoice(invoiceId); foreach (var paymentMethod in invoice.GetPaymentMethods(_NetworkProvider) .Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike)) { @@ -194,7 +194,7 @@ namespace BTCPayServer.Payments.Lightning }, network.CryptoCode, accounted: true); if (payment != null) { - var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId); + var invoice = await _InvoiceRepository.GetInvoice(listenedInvoice.InvoiceId); if (invoice != null) _Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment")); } diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index 176290c25..8caa253aa 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -28,11 +28,12 @@ "BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/", "BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true", - "BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true;macaroonfilepath=D:\\admin.macaroon", + "BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true", "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", "ASPNETCORE_ENVIRONMENT": "Development", "BTCPAY_CHAINS": "btc,ltc", - "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver" + "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", + "BTCPAY_EXTERNALSERVICES": "totoservice:totolink;" }, "applicationUrl": "https://localhost:14142/" } diff --git a/BTCPayServer/Security/BitpayAuthentication.cs b/BTCPayServer/Security/BitpayAuthentication.cs index beb769a66..e71c9f2db 100644 --- a/BTCPayServer/Security/BitpayAuthentication.cs +++ b/BTCPayServer/Security/BitpayAuthentication.cs @@ -57,43 +57,37 @@ namespace BTCPayServer.Security List claims = new List(); var bitpayAuth = Context.Request.HttpContext.GetBitpayAuth(); string storeId = null; - // Careful, those are not the opposite. failedAuth says if a the tentative failed. - // successAuth, ensure that at least one succeed. - var failedAuth = false; - var successAuth = false; + + bool? success = null; if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id)) { var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims); storeId = result.StoreId; - failedAuth = !result.SuccessAuth; - successAuth = result.SuccessAuth; + success = result.SuccessAuth; } else if (!string.IsNullOrEmpty(bitpayAuth.Authorization)) { storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization); - if (storeId == null) - { - Logs.PayServer.LogDebug("API key check failed"); - failedAuth = true; - } - successAuth = storeId != null; + success = storeId != null; } - if (failedAuth) + if (success.HasValue) { - return AuthenticateResult.Fail("Invalid credentials"); - } - - if (successAuth) - { - if (storeId != null) + if (success.Value) { - claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId)); - var store = await _StoreRepository.FindStore(storeId); - store.AdditionalClaims.AddRange(claims); - Context.Request.HttpContext.SetStoreData(store); + if (storeId != null) + { + claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId)); + var store = await _StoreRepository.FindStore(storeId); + store.AdditionalClaims.AddRange(claims); + Context.Request.HttpContext.SetStoreData(store); + } + return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); + } + else + { + return AuthenticateResult.Fail("Invalid credentials"); } - return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); } } return AuthenticateResult.NoResult(); @@ -148,6 +142,10 @@ namespace BTCPayServer.Security storeId = bitToken.StoreId; } } + else + { + return (storeId, false); + } } catch (FormatException) { } return (storeId, true); diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 9adec9791..b37bf0868 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -395,9 +395,6 @@ namespace BTCPayServer.Services.Invoices var cryptoSuffix = cryptoInfo.CryptoCode == "BTC" ? "" : "/" + cryptoInfo.CryptoCode; cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls() { - BIP72 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}&r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}", - BIP72b = $"{scheme}:?r={ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}")}", - BIP73 = ServerUrl.WithTrailingSlash() + ($"i/{Id}{cryptoSuffix}"), BIP21 = $"{scheme}:{cryptoInfo.Address}?amount={cryptoInfo.Due}", }; } @@ -529,6 +526,42 @@ namespace BTCPayServer.Services.Invoices } #pragma warning restore CS0618 } + + public InvoiceState GetInvoiceState() + { + return new InvoiceState() { Status = Status, ExceptionStatus = ExceptionStatus }; + } + } + + public class InvoiceState + { + public string Status { get; set; } + public string ExceptionStatus { get; set; } + public bool CanMarkComplete() + { + return (Status == "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"); +#pragma warning restore CA1305 // Specify IFormatProvider + } + + public bool CanMarkInvalid() + { + return (Status == "paid") || + (Status == "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"); +#pragma warning restore CA1305 // Specify IFormatProvider; + } + public override string ToString() + { + return Status + (ExceptionStatus == null ? string.Empty : $" ({ExceptionStatus})"); + } } public class PaymentMethodAccounting diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 34d78ebd6..0a846f10d 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -334,13 +334,26 @@ namespace BTCPayServer.Services.Invoices using (var context = _ContextFactory.CreateContext()) { var invoiceData = await context.FindAsync(invoiceId).ConfigureAwait(false); - if (invoiceData?.Status != "paid") + if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkInvalid()) return; invoiceData.Status = "invalid"; + invoiceData.ExceptionStatus = "marked"; await context.SaveChangesAsync().ConfigureAwait(false); } } - public async Task GetInvoice(string storeId, string id, bool inludeAddressData = false) + public async Task UpdatePaidInvoiceToComplete(string invoiceId) + { + using (var context = _ContextFactory.CreateContext()) + { + var invoiceData = await context.FindAsync(invoiceId).ConfigureAwait(false); + if (invoiceData == null || !invoiceData.GetInvoiceState().CanMarkComplete()) + return; + invoiceData.Status = "complete"; + invoiceData.ExceptionStatus = "marked"; + await context.SaveChangesAsync().ConfigureAwait(false); + } + } + public async Task GetInvoice(string id, bool inludeAddressData = false) { using (var context = _ContextFactory.CreateContext()) { @@ -353,9 +366,6 @@ namespace BTCPayServer.Services.Invoices query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices); query = query.Where(i => i.Id == id); - if (storeId != null) - query = query.Where(i => i.StoreDataId == storeId); - var invoice = await query.FirstOrDefaultAsync().ConfigureAwait(false); if (invoice == null) return null; diff --git a/BTCPayServer/Services/LightningConfigurationProvider.cs b/BTCPayServer/Services/LightningConfigurationProvider.cs index c2e6f26a5..cb7842af3 100644 --- a/BTCPayServer/Services/LightningConfigurationProvider.cs +++ b/BTCPayServer/Services/LightningConfigurationProvider.cs @@ -39,7 +39,7 @@ namespace BTCPayServer.Services public class LightningConfigurations { - public List Configurations { get; set; } = new List(); + public List Configurations { get; set; } = new List(); } public class LightningConfiguration { @@ -51,5 +51,16 @@ namespace BTCPayServer.Services public bool SSL { get; set; } public string CertificateThumbprint { get; set; } public string Macaroon { get; set; } + public string RestrictedMacaroon { get; set; } + } + public class LNDRestConfiguration + { + public string ChainType { get; set; } + public string Type { get; set; } + public string CryptoCode { get; set; } + public string Uri { get; set; } + public string Macaroon { get; set; } + public string CertificateThumbprint { get; set; } + public string RestrictedMacaroon { get; set; } } } diff --git a/BTCPayServer/Views/Invoice/ListInvoices.cshtml b/BTCPayServer/Views/Invoice/ListInvoices.cshtml index 9c1061389..a415e5706 100644 --- a/BTCPayServer/Views/Invoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/Invoice/ListInvoices.cshtml @@ -99,23 +99,7 @@ } @invoice.InvoiceId - @if (invoice.Status == "paid") - { - - - - } - else - { - @invoice.Status - } + @invoice.Status @invoice.AmountCurrency @if (invoice.ShowCheckout) @@ -123,9 +107,32 @@ Checkout [^] - - + @if (!invoice.CanMarkStatus) + { + - + } } + @if (invoice.CanMarkStatus) + { + + + } Details @@ -153,28 +160,3 @@ - - - diff --git a/BTCPayServer/Views/Server/LndRestServices.cshtml b/BTCPayServer/Views/Server/LndRestServices.cshtml deleted file mode 100644 index 0b0b01523..000000000 --- a/BTCPayServer/Views/Server/LndRestServices.cshtml +++ /dev/null @@ -1,41 +0,0 @@ -@model LndRestServicesViewModel -@{ - ViewData.SetActivePageAndTitle(ServerNavPages.Services); -} - - -

LND REST

- -
-
-
-
-
- -
- -
-
-

BTCPay exposes LND Rest services for outside consumption. See connection information below

-
- -
- - -
- @if (Model.Macaroon != null) - { -
- - -
- } - @if (Model.CertificateThumbprint != null) - { -
- - -
- } -
-
diff --git a/BTCPayServer/Views/Server/LndGrpcServices.cshtml b/BTCPayServer/Views/Server/LndServices.cshtml similarity index 67% rename from BTCPayServer/Views/Server/LndGrpcServices.cshtml rename to BTCPayServer/Views/Server/LndServices.cshtml index a23b68e5a..ff0fbe69f 100644 --- a/BTCPayServer/Views/Server/LndGrpcServices.cshtml +++ b/BTCPayServer/Views/Server/LndServices.cshtml @@ -4,7 +4,7 @@ } -

LND GRPC

+

LND @Model.ConnectionType

@@ -17,14 +17,14 @@

- BTCPay exposes gRPC services for outside consumption, you will find connection information here.
+ BTCPay exposes LND's @Model.ConnectionType service for outside consumption, you will find connection information here.

QR Code connection

- You can use this QR Code to connect your Zap wallet to your LND instance.
+ You can use this QR Code to connect external software to your LND instance.
This QR Code is only valid for 10 minutes

@@ -61,22 +61,39 @@

Alternatively, you can see the settings by clicking here

-
- - -
-
- - -
- @if(Model.Macaroon != null) + @if (Model.Uri == null) + { +
+ + +
+
+ + +
+ } + else + { +
+ + +
+ } + @if (Model.Macaroon != null) {
} - @if(Model.CertificateThumbprint != null) + @if (Model.RestrictedMacaroon != null) + { +
+ + +
+ } + @if (Model.CertificateThumbprint != null) {
diff --git a/BTCPayServer/Views/Server/Services.cshtml b/BTCPayServer/Views/Server/Services.cshtml index 7451fb2d5..2eb1b6565 100644 --- a/BTCPayServer/Views/Server/Services.cshtml +++ b/BTCPayServer/Views/Server/Services.cshtml @@ -15,8 +15,9 @@
+

Crypto services

- You can get access here to LND (gRPC, Rest) or SSH services exposed by your server + You can get access here to LND (gRPC, Rest) services exposed by your server
@@ -37,31 +38,54 @@ @if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.gRPC) { - See information + See information } else if (lnd.Type == BTCPayServer.Configuration.External.LndTypes.Rest) { - See information + See information } } - @if (Model.HasSSH) - { - - None - SSH - - See information - - - }
+@if (Model.ExternalServices.Count != 0) +{ +
+
+

Other services

+
+ Other external services +
+
+ + + + + + + + + @foreach (var s in Model.ExternalServices) + { + + + + + } + +
NameActions
@s.Name + See information +
+
+
+
+} + @section Scripts { @await Html.PartialAsync("_ValidationScriptsPartial") } diff --git a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml index c3c0b4ad9..7ea390f8e 100644 --- a/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml +++ b/BTCPayServer/Views/Stores/AddDerivationScheme.cshtml @@ -15,7 +15,7 @@
- @if(!Model.Confirmation) + @if (!Model.Confirmation) {
Derivation Scheme
@@ -26,13 +26,19 @@ +
+ Checking if a ledger wallet is connected... +
+