diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index eab5a7643..73239a3ec 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -136,6 +136,9 @@ $(IncludeRazorContentInPack) + + $(IncludeRazorContentInPack) + $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 99c7aea30..d657f6944 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -37,8 +37,8 @@ namespace BTCPayServer.Configuration { get; private set; - } - + } + public string LogFile { get; @@ -68,7 +68,7 @@ namespace BTCPayServer.Configuration public static LogEventLevel GetDebugLogLevel(IConfiguration configuration) { var raw = configuration.GetValue("debugloglevel", nameof(LogEventLevel.Debug)); - return (LogEventLevel)Enum.Parse(typeof(LogEventLevel), raw, true); + return (LogEventLevel)Enum.Parse(typeof(LogEventLevel), raw, true); } public void LoadArgs(IConfiguration conf) @@ -139,7 +139,7 @@ namespace BTCPayServer.Configuration externalLnd($"{net.CryptoCode}.external.lnd.rest", "lnd-rest"); var spark = conf.GetOrDefault($"{net.CryptoCode}.external.spark", string.Empty); - if(spark.Length != 0) + if (spark.Length != 0) { if (!SparkConnectionString.TryParse(spark, out var connectionString)) { @@ -148,14 +148,30 @@ namespace BTCPayServer.Configuration } ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalSpark(connectionString)); } + + var charge = conf.GetOrDefault($"{net.CryptoCode}.external.charge", string.Empty); + if (charge.Length != 0) + { + if (!LightningConnectionString.TryParse(charge, false, out var chargeConnectionString, out var chargeError)) + LightningConnectionString.TryParse("type=charge;" + charge, false, out chargeConnectionString, out chargeError); + + if(chargeConnectionString == null || chargeConnectionString.ConnectionType != LightningConnectionType.Charge) + { + throw new ConfigException($"Invalid setting {net.CryptoCode}.external.charge, " + Environment.NewLine + + $"lightning charge server: 'type=charge;server=https://charge.example.com;api-token=2abdf302...'" + Environment.NewLine + + $"lightning charge server: 'type=charge;server=https://charge.example.com;cookiefilepath=/root/.charge/.cookie'" + Environment.NewLine + + chargeError ?? string.Empty); + } + ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalCharge(chargeConnectionString)); + } } Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray())); var services = conf.GetOrDefault("externalservices", null); - if(services != null) + if (services != null) { - foreach(var service in services.Split(new[] { ';', ',' }) + foreach (var service in services.Split(new[] { ';', ',' }) .Select(p => p.Split(':')) .Where(p => p.Length == 2) .Select(p => (Name: p[0], Link: p[1]))) diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index 9e49c5d87..6717312d7 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -50,7 +50,8 @@ namespace BTCPayServer.Configuration app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue); app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue); app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from Zap wallet (default: empty)", CommandOptionType.SingleValue); - app.Option($"--{crypto}externalspark", $"The connection string to spark server (default: empty)", CommandOptionType.SingleValue); + app.Option($"--{crypto}externalspark", $"Show spark information in Server settings / Server. The connection string to spark server (default: empty)", CommandOptionType.SingleValue); + app.Option($"--{crypto}externalcharge", $"Show lightning charge information in Server settings/Server. The connection string to charge server (default: empty)", CommandOptionType.SingleValue); } return app; } diff --git a/BTCPayServer/Configuration/External/ExternalCharge.cs b/BTCPayServer/Configuration/External/ExternalCharge.cs new file mode 100644 index 000000000..03b8d5c86 --- /dev/null +++ b/BTCPayServer/Configuration/External/ExternalCharge.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Lightning; + +namespace BTCPayServer.Configuration.External +{ + public class ExternalCharge : ExternalService + { + public ExternalCharge(LightningConnectionString connectionString) + { + if (connectionString == null) + throw new ArgumentNullException(nameof(connectionString)); + ConnectionString = connectionString; + } + public LightningConnectionString ConnectionString { get; } + } +} diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 7aab0c07b..3c5e404c0 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -449,6 +449,16 @@ namespace BTCPayServer.Controllers Index = i++, }); } + foreach (var chargeService in _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode)) + { + result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel() + { + Crypto = cryptoCode, + Type = "Lightning charge server", + Action = nameof(LightningChargeServices), + Index = i++, + }); + } } foreach(var externalService in _Options.ExternalServices) { @@ -469,6 +479,45 @@ namespace BTCPayServer.Controllers return View(result); } + [Route("server/services/lightning-charge/{cryptoCode}/{index}")] + public async Task LightningChargeServices(string cryptoCode, int index, bool showQR = false) + { + if (!_dashBoard.IsFullySynched(cryptoCode, out var unusud)) + { + StatusMessage = $"Error: {cryptoCode} is not fully synched"; + return RedirectToAction(nameof(Services)); + } + var lightningCharge = _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode).Select(c => c.ConnectionString).FirstOrDefault(); + if (lightningCharge == null) + { + return NotFound(); + } + + ChargeServiceViewModel vm = new ChargeServiceViewModel(); + vm.Uri = lightningCharge.ToUri(false).AbsoluteUri; + vm.APIToken = lightningCharge.Password; + try + { + if (string.IsNullOrEmpty(vm.APIToken) && lightningCharge.CookieFilePath != null) + { + if (lightningCharge.CookieFilePath != "fake") + vm.APIToken = await System.IO.File.ReadAllTextAsync(lightningCharge.CookieFilePath); + else + vm.APIToken = "fake"; + } + var builder = new UriBuilder(lightningCharge.ToUri(false)); + builder.UserName = "api-token"; + builder.Password = vm.APIToken; + vm.AuthenticatedUri = builder.ToString(); + } + catch (Exception ex) + { + StatusMessage = $"Error: {ex.Message}"; + return RedirectToAction(nameof(Services)); + } + return View(vm); + } + [Route("server/services/spark/{cryptoCode}/{index}")] public async Task SparkServices(string cryptoCode, int index, bool showQR = false) { @@ -477,7 +526,7 @@ namespace BTCPayServer.Controllers StatusMessage = $"Error: {cryptoCode} is not fully synched"; return RedirectToAction(nameof(Services)); } - var spark = _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode).Skip(index).Select(c => c.ConnectionString).FirstOrDefault(); + var spark = _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode).Select(c => c.ConnectionString).FirstOrDefault(); if(spark == null) { return NotFound(); diff --git a/BTCPayServer/Models/ServerViewModels/ChargeServiceViewModel.cs b/BTCPayServer/Models/ServerViewModels/ChargeServiceViewModel.cs new file mode 100644 index 000000000..25b8ac4f6 --- /dev/null +++ b/BTCPayServer/Models/ServerViewModels/ChargeServiceViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Models.ServerViewModels +{ + public class ChargeServiceViewModel + { + public string Uri { get; set; } + public string APIToken { get; set; } + public string AuthenticatedUri { get; set; } + } +} diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index 5bfafb34c..226da05de 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -30,6 +30,7 @@ "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", "BTCPAY_BTCEXTERNALSPARK": "server=https://127.0.0.1:53280/spark/btc/;cookiefile=fake", + "BTCPAY_BTCEXTERNALCHARGE": "server=https://127.0.0.1:53280/spark/btc/;cookiefilepath=fake", "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", "ASPNETCORE_ENVIRONMENT": "Development", "BTCPAY_CHAINS": "btc,ltc", diff --git a/BTCPayServer/Views/Server/LightningChargeServices.cshtml b/BTCPayServer/Views/Server/LightningChargeServices.cshtml new file mode 100644 index 000000000..6bc4e9946 --- /dev/null +++ b/BTCPayServer/Views/Server/LightningChargeServices.cshtml @@ -0,0 +1,60 @@ +@model ChargeServiceViewModel +@{ + ViewData.SetActivePageAndTitle(ServerNavPages.Services); +} + + +

Lightning charge service

+ + +
+
+
+
+
+ +
+
+
+

+ Lightning charge is a simple API for invoicing on lightning network, you can use it with several plugins: +

    +
  • WooCommerce Lightning Gateway: A comprehensive e-commerce application that integrates with stock-management and order-tracking systems
  • +
  • Nanopos: A simple point-of-sale system for fixed-price goods
  • +
  • FileBazaar: A system for selling files such as documents, images, and videos
  • +
  • Lightning Publisher for WordPress: A patronage model for unlocking WordPress blog entries
  • +
  • Paypercall: A programmer’s toolkit for Lightning that enables micropayments for individual API calls
  • +
  • Ifpaytt: An extension of paypercall that allows web developers using IFTTT to request payments for service usage
  • +
  • Lightning Jukebox: A fun demo that reimagines a classic technology for the Lightning Network
  • +
  • Nanotip: The simple tip jar, rebuilt to issue Lightning Network invoices
  • +
+

+
+
+
+
+
+

Credentials

+ @if (Model.Uri != null) + { +
+ + +
+ } + @if (Model.APIToken != null) + { +
+ + +
+ } + @if (Model.AuthenticatedUri != null) + { +
+ + +
+ } +
+