mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-11 01:35:22 +01:00
LNURL Payment Method Support (#2897)
* LNURL Payment Method Support
* Merge recent Lightning controller related changes
* Fix build
* Create separate payment settings section for stores
* Improve LNURL configuration
* Prevent duplicate array entries when merging Swagger JSON
* Fix CanSetPaymentMethodLimitsLightning
* Fix CanUsePayjoinViaUI
* Adapt test for new cancel bolt invoice feature
* rebase fixes
* Fixes after rebase
* Test fixes
* Do not turn LNURL on by default, Off-Chain payment criteria should affects both BOLT11 and LNURL, Payment criteria of unset payment method shouldn't be shown
* Send better error if payment method not found
* Revert "Prevent duplicate array entries when merging Swagger JSON"
This reverts commit 5783db9eda
.
* Fix LNUrl doc
* Fix some warnings
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
fbdd2fc470
commit
951bfeefb1
58 changed files with 1996 additions and 480 deletions
|
@ -29,7 +29,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="6.0.15" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="icon.png" Pack="true" PackagePath="\" />
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<IEnumerable<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (enabled != null)
|
||||
{
|
||||
query.Add(nameof(enabled), enabled);
|
||||
}
|
||||
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay",
|
||||
query), token);
|
||||
return await HandleResponse<IEnumerable<LNURLPayPaymentMethodData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}"), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task RemoveStoreLNURLPayPaymentMethod(string storeId,
|
||||
string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
method: HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode, LNURLPayPaymentMethodData paymentMethod,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}",
|
||||
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
|
||||
return await HandleResponse<LNURLPayPaymentMethodData>(response);
|
||||
}
|
||||
}
|
||||
}
|
14
BTCPayServer.Client/Models/LNURLPayPaymentMethodBaseData.cs
Normal file
14
BTCPayServer.Client/Models/LNURLPayPaymentMethodBaseData.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
public bool EnableForStandardInvoices { get; set; }
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodBaseData()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
27
BTCPayServer.Client/Models/LNURLPayPaymentMethodData.cs
Normal file
27
BTCPayServer.Client/Models/LNURLPayPaymentMethodData.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LNURLPayPaymentMethodData: LNURLPayPaymentMethodBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the payment method is enabled
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Crypto code of the payment method
|
||||
/// </summary>
|
||||
public string CryptoCode { get; set; }
|
||||
|
||||
public LNURLPayPaymentMethodData()
|
||||
{
|
||||
}
|
||||
|
||||
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool enableForStandardInvoices)
|
||||
{
|
||||
Enabled = enabled;
|
||||
CryptoCode = cryptoCode;
|
||||
UseBech32Scheme = useBech32Scheme;
|
||||
EnableForStandardInvoices = enableForStandardInvoices;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ namespace BTCPayServer.Data
|
|||
public string? Destination { get; set; }
|
||||
#nullable restore
|
||||
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
builder.Entity<PayoutData>()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.11.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="6.0.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Get enabled state from overview action
|
||||
StoreViewModel storeModel;
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var lnNode = storeModel.LightningNodes.Find(node => node.CryptoCode == cryptoCode);
|
||||
Assert.NotNull(lnNode);
|
||||
|
@ -89,7 +89,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get enabled state from overview action
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
var derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
|
@ -98,7 +98,7 @@ namespace BTCPayServer.Tests
|
|||
// Disable wallet
|
||||
response = controller.SetWalletEnabled(storeId, cryptoCode, false).GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.UpdateStore();
|
||||
response = controller.UpdateStore();
|
||||
storeModel = (StoreViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
derivationScheme = storeModel.DerivationSchemes.Find(scheme => scheme.Crypto == cryptoCode);
|
||||
Assert.NotNull(derivationScheme);
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="94.0.4606.6100" />
|
||||
|
|
|
@ -137,10 +137,10 @@ namespace BTCPayServer.Tests
|
|||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.AddLightningNode();
|
||||
s.GoToStore(store.storeId);
|
||||
s.GoToStore(store.storeId, StoreNavPages.Payment);
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);
|
||||
Assert.Contains("Payment settings successfully updated", s.FindAlertMessage().Text);
|
||||
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
|
|
|
@ -165,7 +165,7 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
var apps = user.GetController<AppsController>();
|
||||
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
|
||||
vm.Name = "test";
|
||||
|
|
|
@ -2042,7 +2042,7 @@ namespace BTCPayServer.Tests
|
|||
void VerifyLightning(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.LightningLike).ToStringNormalized(), out var item));
|
||||
var lightningNetworkPaymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
var lightningNetworkPaymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<LightningNetworkPaymentMethodBaseData>();
|
||||
Assert.Equal("Internal Node", lightningNetworkPaymentMethodBaseData.ConnectionString);
|
||||
}
|
||||
|
||||
|
@ -2057,7 +2057,7 @@ namespace BTCPayServer.Tests
|
|||
void VerifyOnChain(Dictionary<string, GenericPaymentMethodData> dictionary)
|
||||
{
|
||||
Assert.True(dictionary.TryGetValue(new PaymentMethodId("BTC", PaymentTypes.BTCLike).ToStringNormalized(), out var item));
|
||||
var paymentMethodBaseData =Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
||||
var paymentMethodBaseData = Assert.IsType<JObject>(item.Data).ToObject<OnChainPaymentMethodBaseData>();
|
||||
Assert.Equal(randK, paymentMethodBaseData.DerivationScheme);
|
||||
}
|
||||
|
||||
|
@ -2091,6 +2091,5 @@ namespace BTCPayServer.Tests
|
|||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ using BTCPayServer.Views.Wallets;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
|
@ -301,7 +302,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}=", bip21);
|
||||
|
||||
s.GoToHome();
|
||||
s.GoToStore(receiver.storeId);
|
||||
s.GoToStore(receiver.storeId, StoreNavPages.Payment);
|
||||
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
||||
|
||||
var sender = s.CreateNewStore();
|
||||
|
@ -570,9 +571,9 @@ namespace BTCPayServer.Tests
|
|||
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
||||
await notifications.NextEventAsync();
|
||||
await bob.ModifyStore(s => s.PayJoinEnabled = true);
|
||||
await bob.ModifyPayment(p => p.PayJoinEnabled = true);
|
||||
var invoice = bob.BitPay.CreateInvoice(
|
||||
new Invoice() { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
new Invoice { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
await RegisterAsync(isAdmin);
|
||||
await CreateStoreAsync();
|
||||
var store = this.GetController<StoresController>();
|
||||
var store = GetController<StoresController>();
|
||||
var pairingCode = BitPay.RequestClientAuthorization("test", Facade.Merchant);
|
||||
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
|
||||
await store.Pair(pairingCode.ToString(), StoreId);
|
||||
|
@ -127,19 +127,19 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public async Task SetNetworkFeeMode(NetworkFeeMode mode)
|
||||
{
|
||||
await ModifyStore(store =>
|
||||
await ModifyPayment(payment =>
|
||||
{
|
||||
store.NetworkFeeMode = mode;
|
||||
payment.NetworkFeeMode = mode;
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ModifyStore(Action<StoreViewModel> modify)
|
||||
public async Task ModifyPayment(Action<PaymentViewModel> modify)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
var response = await storeController.UpdateStore();
|
||||
StoreViewModel store = (StoreViewModel)((ViewResult)response).Model;
|
||||
modify(store);
|
||||
storeController.UpdateStore(store).GetAwaiter().GetResult();
|
||||
var response = await storeController.Payment();
|
||||
PaymentViewModel payment = (PaymentViewModel)((ViewResult)response).Model;
|
||||
modify(payment);
|
||||
await storeController.Payment(payment);
|
||||
}
|
||||
|
||||
public T GetController<T>(bool setImplicitStore = true) where T : Controller
|
||||
|
@ -190,7 +190,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public Task EnablePayJoin()
|
||||
{
|
||||
return ModifyStore(s => s.PayJoinEnabled = true);
|
||||
return ModifyPayment(p => p.PayJoinEnabled = true);
|
||||
}
|
||||
|
||||
public GenerateWalletResponse GenerateWalletResponseV { get; set; }
|
||||
|
@ -240,23 +240,26 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public bool IsAdmin { get; internal set; }
|
||||
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true, Action<LightningNodeViewModel> setViewModel = null)
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant, setViewModel: setViewModel).GetAwaiter().GetResult();
|
||||
}
|
||||
public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null)
|
||||
public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null, Action<LightningNodeViewModel> setViewModel = null)
|
||||
{
|
||||
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
|
||||
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId, setViewModel);
|
||||
}
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null, Action<LightningNodeViewModel> setViewModel = null)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
|
||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
||||
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
|
||||
var vm = new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true };
|
||||
if (setViewModel != null)
|
||||
setViewModel(vm);
|
||||
await storeController.SetupLightningNode(storeId ?? StoreId,
|
||||
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", cryptoCode);
|
||||
vm, "save", cryptoCode);
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ using BTCPayServer.Fido2.Models;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
|
@ -818,11 +819,11 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Set tolerance to 50%
|
||||
var stores = user.GetController<StoresController>();
|
||||
var response = await stores.UpdateStore();
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
var response = await stores.Payment();
|
||||
var vm = Assert.IsType<PaymentViewModel>(Assert.IsType<ViewResult>(response).Model);
|
||||
Assert.Equal(0.0, vm.PaymentTolerance);
|
||||
vm.PaymentTolerance = 50.0;
|
||||
Assert.IsType<RedirectToActionResult>(stores.UpdateStore(vm).Result);
|
||||
Assert.IsType<RedirectToActionResult>(stores.Payment(vm).Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
|
@ -996,8 +997,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(4, tor.Services.Length);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
|
@ -1012,7 +1012,7 @@ namespace BTCPayServer.Tests
|
|||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
await user.ModifyStore(model => model.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
await user.ModifyPayment(p => p.SpeedPolicy = SpeedPolicy.HighSpeed);
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.0001m, "BTC"));
|
||||
await tester.WaitForEvent<InvoiceNewPaymentDetailsEvent>(async () =>
|
||||
{
|
||||
|
@ -1042,14 +1042,22 @@ namespace BTCPayServer.Tests
|
|||
Assert.Contains(fetchedInvoice.Status, new[] { InvoiceStatusLegacy.Complete, InvoiceStatusLegacy.Confirmed });
|
||||
Assert.Equal(InvoiceExceptionStatus.None, fetchedInvoice.ExceptionStatus);
|
||||
|
||||
Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
//BTCPay will attempt to cancel previous bolt11 invoices so that there are less weird edge case scenarios
|
||||
Logs.Tester.LogInformation($"Attempting to pay invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
await Assert.ThrowsAsync<LightningRPCException>(async () =>
|
||||
{
|
||||
await tester.SendLightningPaymentAsync(invoice);
|
||||
}, evt => evt.InvoiceId == invoice.Id);
|
||||
Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
});
|
||||
|
||||
//NOTE: Eclair does not support cancelling invoice so the below test case would make sense for it
|
||||
// Logs.Tester.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
|
||||
// evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
|
||||
// {
|
||||
// await tester.SendLightningPaymentAsync(invoice);
|
||||
// }, evt => evt.InvoiceId == invoice.Id);
|
||||
// Assert.Equal(evt.InvoiceId, invoice.Id);
|
||||
// fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
|
||||
// Assert.Equal(3, fetchedInvoice.Payments.Count);
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
|
@ -1065,7 +1073,7 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var storeResponse = await storeController.UpdateStore();
|
||||
var storeResponse = storeController.UpdateStore();
|
||||
Assert.IsType<ViewResult>(storeResponse);
|
||||
Assert.IsType<ViewResult>(await storeController.SetupLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
|
@ -1089,7 +1097,7 @@ namespace BTCPayServer.Tests
|
|||
new LightningNodeViewModel { ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri },
|
||||
"save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
storeResponse = await storeController.UpdateStore();
|
||||
storeResponse = storeController.UpdateStore();
|
||||
var storeVm =
|
||||
Assert.IsType<StoreViewModel>(Assert
|
||||
.IsType<ViewResult>(storeResponse).Model);
|
||||
|
@ -1205,7 +1213,7 @@ namespace BTCPayServer.Tests
|
|||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
await acc.ModifyStore(s => s.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
await acc.ModifyPayment(p => p.SpeedPolicy = SpeedPolicy.LowSpeed);
|
||||
var invoice = acc.BitPay.CreateInvoice(new Invoice
|
||||
{
|
||||
Price = 5.0m,
|
||||
|
@ -2032,7 +2040,7 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
Assert.Equal(404, (int)response.StatusCode);
|
||||
|
||||
await user.ModifyStore(s => s.AnyoneCanCreateInvoice = true);
|
||||
await user.ModifyPayment(p => p.AnyoneCanCreateInvoice = true);
|
||||
|
||||
Logs.Tester.LogInformation("Bad store with anyone can create invoice = 403");
|
||||
response = await tester.PayTester.HttpClient.SendAsync(
|
||||
|
@ -2306,7 +2314,7 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
await user.ModifyStore(s =>
|
||||
await user.ModifyPayment(s =>
|
||||
{
|
||||
Assert.Equal("USD", s.DefaultCurrency);
|
||||
s.DefaultCurrency = "EUR";
|
||||
|
@ -2357,7 +2365,7 @@ namespace BTCPayServer.Tests
|
|||
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
Assert.Equal(2, vm.PaymentMethodCriteria.Count);
|
||||
Assert.Equal(3, vm.PaymentMethodCriteria.Count);
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
|
||||
Assert.Equal(new PaymentMethodId("BTC", BitcoinPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "5 USD";
|
||||
|
@ -2448,12 +2456,12 @@ namespace BTCPayServer.Tests
|
|||
Assert.DoesNotContain("&lightning=", paymentMethodFirst.InvoiceBitcoinUrlQR);
|
||||
|
||||
// enable unified QR code in settings
|
||||
var vm = Assert.IsType<StoreViewModel>(Assert
|
||||
.IsType<ViewResult>(await user.GetController<StoresController>().UpdateStore()).Model
|
||||
var vm = Assert.IsType<PaymentViewModel>(Assert
|
||||
.IsType<ViewResult>(await user.GetController<StoresController>().Payment()).Model
|
||||
);
|
||||
vm.OnChainWithLnInvoiceFallback = true;
|
||||
Assert.IsType<RedirectToActionResult>(
|
||||
user.GetController<StoresController>().UpdateStore(vm).Result
|
||||
user.GetController<StoresController>().Payment(vm).Result
|
||||
);
|
||||
|
||||
// validate that QR code now has both onchain and offchain payment urls
|
||||
|
@ -2470,7 +2478,7 @@ namespace BTCPayServer.Tests
|
|||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
|
||||
// Fallback lightning invoice should be uppercase inside the QR code.
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new string[] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
var lightningFallback = paymentMethodSecond.InvoiceBitcoinUrlQR.Split(new [] { "&lightning=" }, StringSplitOptions.None)[1];
|
||||
Assert.True(lightningFallback.ToUpperInvariant() == lightningFallback);
|
||||
}
|
||||
}
|
||||
|
@ -2488,10 +2496,8 @@ namespace BTCPayServer.Tests
|
|||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
Assert.Single(vm.PaymentMethodCriteria);
|
||||
var criteria = vm.PaymentMethodCriteria.First();
|
||||
var vm = user.GetController<StoresController>().CheckoutExperience().AssertViewModel<CheckoutExperienceViewModel>();
|
||||
var criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
criteria.Value = "2 USD";
|
||||
criteria.Type = PaymentMethodCriteriaViewModel.CriteriaType.LessThan;
|
||||
|
@ -2499,18 +2505,42 @@ namespace BTCPayServer.Tests
|
|||
.Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge, setViewModel: vm =>
|
||||
{
|
||||
vm.LNURLEnabled = true;
|
||||
vm.LNURLStandardInvoiceEnabled = true;
|
||||
});
|
||||
vm = user.GetController<StoresController>().CheckoutExperience().AssertViewModel<CheckoutExperienceViewModel>();
|
||||
criteria = Assert.Single(vm.PaymentMethodCriteria);
|
||||
Assert.Equal(new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString(), criteria.PaymentMethod);
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm).Result);
|
||||
|
||||
// However, creating an invoice should show LNURL
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant);
|
||||
Assert.Equal(2, invoice.CryptoInfo.Length);
|
||||
|
||||
// Make sure this throw: Since BOLT11 and LN Url share the same criteria, there should be no payment method available
|
||||
Assert.Throws<BitPayException>(() => user.BitPay.CreateInvoice(
|
||||
new Invoice
|
||||
{
|
||||
Price = 2.5m,
|
||||
Currency = "USD"
|
||||
}, Facade.Merchant));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
using YamlDotNet.Core.Tokens;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
using Language = BTCPayServer.Client.Models.Language;
|
||||
using NotificationData = BTCPayServer.Client.Models.NotificationData;
|
||||
|
@ -37,6 +36,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
private readonly StoreOnChainPaymentMethodsController _chainPaymentMethodsController;
|
||||
private readonly StoreOnChainWalletsController _storeOnChainWalletsController;
|
||||
private readonly StoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
private readonly StoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly HealthController _healthController;
|
||||
private readonly GreenFieldPaymentRequestsController _paymentRequestController;
|
||||
private readonly ApiKeysController _apiKeysController;
|
||||
|
@ -58,6 +58,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
StoreOnChainPaymentMethodsController chainPaymentMethodsController,
|
||||
StoreOnChainWalletsController storeOnChainWalletsController,
|
||||
StoreLightningNetworkPaymentMethodsController storeLightningNetworkPaymentMethodsController,
|
||||
StoreLNURLPayPaymentMethodsController storeLnurlPayPaymentMethodsController,
|
||||
HealthController healthController,
|
||||
GreenFieldPaymentRequestsController paymentRequestController,
|
||||
ApiKeysController apiKeysController,
|
||||
|
@ -79,6 +80,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
_chainPaymentMethodsController = chainPaymentMethodsController;
|
||||
_storeOnChainWalletsController = storeOnChainWalletsController;
|
||||
_storeLightningNetworkPaymentMethodsController = storeLightningNetworkPaymentMethodsController;
|
||||
_storeLnurlPayPaymentMethodsController = storeLnurlPayPaymentMethodsController;
|
||||
_healthController = healthController;
|
||||
_paymentRequestController = paymentRequestController;
|
||||
_apiKeysController = apiKeysController;
|
||||
|
@ -141,6 +143,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
_storeLightningNodeApiController,
|
||||
_internalLightningNodeApiController,
|
||||
_storeLightningNetworkPaymentMethodsController,
|
||||
_storeLnurlPayPaymentMethodsController,
|
||||
_greenFieldInvoiceController,
|
||||
_greenFieldServerInfoController,
|
||||
_storeWebhooksController,
|
||||
|
@ -165,6 +168,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
private readonly StoreLightningNodeApiController _storeLightningNodeApiController;
|
||||
private readonly InternalLightningNodeApiController _lightningNodeApiController;
|
||||
private readonly StoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
|
||||
private readonly StoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
|
||||
private readonly GreenFieldInvoiceController _greenFieldInvoiceController;
|
||||
private readonly GreenFieldServerInfoController _greenFieldServerInfoController;
|
||||
private readonly StoreWebhooksController _storeWebhooksController;
|
||||
|
@ -183,6 +187,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
StoreLightningNodeApiController storeLightningNodeApiController,
|
||||
InternalLightningNodeApiController lightningNodeApiController,
|
||||
StoreLightningNetworkPaymentMethodsController storeLightningNetworkPaymentMethodsController,
|
||||
StoreLNURLPayPaymentMethodsController storeLnurlPayPaymentMethodsController,
|
||||
GreenFieldInvoiceController greenFieldInvoiceController,
|
||||
GreenFieldServerInfoController greenFieldServerInfoController,
|
||||
StoreWebhooksController storeWebhooksController,
|
||||
|
@ -202,6 +207,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
_storeLightningNodeApiController = storeLightningNodeApiController;
|
||||
_lightningNodeApiController = lightningNodeApiController;
|
||||
_storeLightningNetworkPaymentMethodsController = storeLightningNetworkPaymentMethodsController;
|
||||
_storeLnurlPayPaymentMethodsController = storeLnurlPayPaymentMethodsController;
|
||||
_greenFieldInvoiceController = greenFieldInvoiceController;
|
||||
_greenFieldServerInfoController = greenFieldServerInfoController;
|
||||
_storeWebhooksController = storeWebhooksController;
|
||||
|
@ -746,7 +752,39 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return GetFromActionResult<StoreData>(await _storesController.UpdateStore(storeId, request));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LNURLPayPaymentMethodData>>
|
||||
GetStoreLNURLPayPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult(
|
||||
_storeLnurlPayPaymentMethodsController.GetLNURLPayPaymentMethods(storeId, enabled)));
|
||||
}
|
||||
|
||||
public override Task<LNURLPayPaymentMethodData> GetStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(GetFromActionResult<LNURLPayPaymentMethodData>(
|
||||
_storeLnurlPayPaymentMethodsController.GetLNURLPayPaymentMethod(storeId, cryptoCode)));
|
||||
}
|
||||
|
||||
public override async Task RemoveStoreLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
HandleActionResult(
|
||||
await _storeLnurlPayPaymentMethodsController.RemoveLNURLPayPaymentMethod(storeId,
|
||||
cryptoCode));
|
||||
}
|
||||
|
||||
public override async Task<LNURLPayPaymentMethodData> UpdateStoreLNURLPayPaymentMethod(
|
||||
string storeId, string cryptoCode,
|
||||
LNURLPayPaymentMethodData paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
return GetFromActionResult<LNURLPayPaymentMethodData>(await
|
||||
_storeLnurlPayPaymentMethodsController.UpdateLNURLPayPaymentMethod(storeId, cryptoCode,
|
||||
paymentMethod));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<LightningNetworkPaymentMethodData>>
|
||||
GetStoreLightningNetworkPaymentMethods(string storeId, bool? enabled,
|
||||
CancellationToken token = default)
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public class StoreLNURLPayPaymentMethodsController : ControllerBase
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ISettingsRepository _settingsRepository;
|
||||
|
||||
public StoreLNURLPayPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IAuthorizationService authorizationService,
|
||||
ISettingsRepository settingsRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_authorizationService = authorizationService;
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public static IEnumerable<LNURLPayPaymentMethodData> GetLNURLPayPaymentMethods(StoreData store,
|
||||
BTCPayNetworkProvider networkProvider, bool? enabled)
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
|
||||
|
||||
return store.GetSupportedPaymentMethods(networkProvider)
|
||||
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LNURLPay)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.Select(paymentMethod =>
|
||||
new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excludedPaymentMethods.Match(paymentMethod.PaymentId),
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
)
|
||||
)
|
||||
.Where((result) => enabled is null || enabled == result.Enabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay")]
|
||||
public ActionResult<IEnumerable<LNURLPayPaymentMethodData>> GetLNURLPayPaymentMethods(
|
||||
string storeId,
|
||||
[FromQuery] bool? enabled)
|
||||
{
|
||||
return Ok(GetLNURLPayPaymentMethods(Store, _btcPayNetworkProvider, enabled));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public IActionResult GetLNURLPayPaymentMethod(string storeId, string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var method = GetExistingLNURLPayPaymentMethod(cryptoCode);
|
||||
if (method is null)
|
||||
{
|
||||
return this.CreateAPIError(404, "paymentmethod-not-found", "The LNURL Payment Method isn't activated");
|
||||
}
|
||||
|
||||
return Ok(method);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLNURLPayPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var store = Store;
|
||||
store.SetSupportedPaymentMethod(id, null);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LNURLPay/{cryptoCode}")]
|
||||
public async Task<IActionResult> UpdateLNURLPayPaymentMethod(string storeId, string cryptoCode,
|
||||
[FromBody] LNURLPayPaymentMethodData paymentMethodData)
|
||||
{
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
|
||||
if (!GetNetwork(cryptoCode, out var network))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var lnMethod = StoreLightningNetworkPaymentMethodsController.GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider,
|
||||
cryptoCode, Store);
|
||||
|
||||
if ((lnMethod is null || lnMethod.Enabled is false) && paymentMethodData.Enabled)
|
||||
{
|
||||
ModelState.AddModelError(nameof(LNURLPayPaymentMethodData.Enabled),
|
||||
"LNURL Pay cannot be enabled unless the lightning payment method is configured and enabled on this store");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
LNURLPaySupportedPaymentMethod? paymentMethod = new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
UseBech32Scheme = paymentMethodData.UseBech32Scheme,
|
||||
EnableForStandardInvoices = paymentMethodData.EnableForStandardInvoices
|
||||
};
|
||||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLNURLPayPaymentMethod(cryptoCode, store));
|
||||
}
|
||||
|
||||
private LNURLPayPaymentMethodData? GetExistingLNURLPayPaymentMethod(string cryptoCode,
|
||||
StoreData? store = null)
|
||||
{
|
||||
store ??= Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
var excluded = storeBlob.IsExcluded(id);
|
||||
return paymentMethod is null
|
||||
? null
|
||||
: new LNURLPayPaymentMethodData(
|
||||
paymentMethod.PaymentId.CryptoCode,
|
||||
!excluded,
|
||||
paymentMethod.UseBech32Scheme, paymentMethod.EnableForStandardInvoices
|
||||
);
|
||||
}
|
||||
|
||||
private bool GetNetwork(string cryptoCode, [MaybeNullWhen(false)] out BTCPayNetwork network)
|
||||
{
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
network = network?.SupportLightning is true ? network : null;
|
||||
return network != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,7 +84,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
var method = GetExistingLightningLikePaymentMethod(cryptoCode);
|
||||
var method = GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, Store);
|
||||
if (method is null)
|
||||
{
|
||||
return NotFound();
|
||||
|
@ -97,8 +97,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public async Task<IActionResult> RemoveLightningNetworkPaymentMethod(
|
||||
string storeId,
|
||||
string cryptoCode,
|
||||
int offset = 0, int amount = 10)
|
||||
string cryptoCode)
|
||||
{
|
||||
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
|
||||
{
|
||||
|
@ -188,17 +187,17 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
storeBlob.SetExcluded(paymentMethodId, !request.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLightningLikePaymentMethod(cryptoCode, store));
|
||||
return Ok(GetExistingLightningLikePaymentMethod(_btcPayNetworkProvider, cryptoCode, store));
|
||||
}
|
||||
|
||||
private LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(string cryptoCode,
|
||||
StoreData? store = null)
|
||||
public static LightningNetworkPaymentMethodData? GetExistingLightningLikePaymentMethod(BTCPayNetworkProvider btcPayNetworkProvider, string cryptoCode,
|
||||
StoreData store)
|
||||
{
|
||||
store ??= Store;
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var paymentMethod = store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.GetSupportedPaymentMethods(btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(method => method.PaymentId == id);
|
||||
|
||||
|
|
|
@ -374,7 +374,8 @@ namespace BTCPayServer.Controllers
|
|||
Overpaid = _CurrencyNameTable.DisplayFormatCurrency(
|
||||
accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode),
|
||||
Address = data.GetPaymentMethodDetails().GetPaymentDestination(),
|
||||
Rate = ExchangeRate(data)
|
||||
Rate = ExchangeRate(data),
|
||||
PaymentMethodRaw = data
|
||||
};
|
||||
}).ToList()
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
@ -41,7 +42,8 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
var paymentMethodDetails = GetExistingLightningSupportedPaymentMethod(cryptoCode, store);
|
||||
var network = _BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var nodeInfo = await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, network);
|
||||
var nodeInfo =
|
||||
await _LightningLikePaymentHandler.GetNodeInfo(paymentMethodDetails, network, new InvoiceLogs());
|
||||
|
||||
return View(new ShowLightningNodeInfoViewModel
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
|
@ -48,7 +49,6 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
|
||||
LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (vm.LightningNodeType == LightningNodeType.Internal)
|
||||
|
@ -92,6 +92,7 @@ namespace BTCPayServer.Controllers
|
|||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningUrl(connectionString);
|
||||
|
||||
}
|
||||
|
||||
switch (command)
|
||||
|
@ -99,8 +100,22 @@ namespace BTCPayServer.Controllers
|
|||
case "save":
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.Hints.Lightning = false;
|
||||
|
||||
var lnurl = new PaymentMethodId(vm.CryptoCode, PaymentTypes.LNURLPay);
|
||||
storeBlob.SetExcluded(lnurl, !vm.LNURLEnabled);
|
||||
store.SetSupportedPaymentMethod(new LNURLPaySupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = vm.CryptoCode,
|
||||
EnableForStandardInvoices = vm.LNURLStandardInvoiceEnabled,
|
||||
UseBech32Scheme = vm.LNURLBech32Mode,
|
||||
LUD12Enabled = vm.LUD12Enabled
|
||||
});
|
||||
|
||||
store.SetStoreBlob(storeBlob);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
|
||||
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node updated.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId });
|
||||
|
@ -109,7 +124,7 @@ namespace BTCPayServer.Controllers
|
|||
var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>();
|
||||
try
|
||||
{
|
||||
var info = await handler.GetNodeInfo(paymentMethod, network, Request.IsOnion());
|
||||
var info = await handler.GetNodeInfo(paymentMethod, network, new InvoiceLogs(), Request.IsOnion());
|
||||
if (!vm.SkipPortTest)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
|
@ -163,7 +178,9 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
vm.CanUseInternalNode = await CanUseInternalLightning();
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lightning != null)
|
||||
|
||||
var lnSet = lightning != null;
|
||||
if (lnSet)
|
||||
{
|
||||
vm.LightningNodeType = lightning.IsInternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
vm.ConnectionString = lightning.GetDisplayableConnectionString();
|
||||
|
@ -172,6 +189,20 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
vm.LightningNodeType = vm.CanUseInternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
}
|
||||
|
||||
var lnurl = GetExistingLNURLSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lnurl != null)
|
||||
{
|
||||
vm.LNURLEnabled = !store.GetStoreBlob().GetExcludedPaymentMethods().Match(lnurl.PaymentId);
|
||||
vm.LNURLBech32Mode = lnurl.UseBech32Scheme;
|
||||
vm.LNURLStandardInvoiceEnabled = lnurl.EnableForStandardInvoices;
|
||||
vm.LUD12Enabled = lnurl.LUD12Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.LNURLEnabled = !lnSet;
|
||||
vm.DisableBolt11PaymentMethod = false;
|
||||
}
|
||||
}
|
||||
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
|
@ -182,5 +213,13 @@ namespace BTCPayServer.Controllers
|
|||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
private LNURLPaySupportedPaymentMethod GetExistingLNURLSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LNURLPaySupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,7 +372,11 @@ namespace BTCPayServer.Controllers
|
|||
var storeBlob = CurrentStore.GetStoreBlob();
|
||||
var vm = new CheckoutExperienceViewModel();
|
||||
SetCryptoCurrencies(vm, CurrentStore);
|
||||
vm.PaymentMethodCriteria = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider).Select(method =>
|
||||
vm.PaymentMethodCriteria = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !storeBlob.GetExcludedPaymentMethods().Match(s.PaymentId))
|
||||
.Where(s => _NetworkProvider.GetNetwork(s.PaymentId.CryptoCode) != null)
|
||||
.Where(s => s.PaymentId.PaymentType != PaymentTypes.LNURLPay)
|
||||
.Select(method =>
|
||||
{
|
||||
var existing =
|
||||
storeBlob.PaymentMethodCriteria.SingleOrDefault(criteria =>
|
||||
|
@ -461,13 +465,36 @@ namespace BTCPayServer.Controllers
|
|||
return View(model);
|
||||
}
|
||||
|
||||
blob.PaymentMethodCriteria = model.PaymentMethodCriteria
|
||||
.Where(viewModel => !string.IsNullOrEmpty(viewModel.Value)).Select(viewModel =>
|
||||
// Payment criteria for Off-Chain should also affect LNUrl
|
||||
foreach (var newCriteria in model.PaymentMethodCriteria.ToList())
|
||||
{
|
||||
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
|
||||
if (paymentMethodId.PaymentType == PaymentTypes.LightningLike)
|
||||
model.PaymentMethodCriteria.Add(new PaymentMethodCriteriaViewModel()
|
||||
{
|
||||
PaymentMethod = new PaymentMethodId(paymentMethodId.CryptoCode, PaymentTypes.LNURLPay).ToString(),
|
||||
Type = newCriteria.Type,
|
||||
Value = newCriteria.Value
|
||||
});
|
||||
// Should not be able to set LNUrlPay criteria directly in UI
|
||||
if (paymentMethodId.PaymentType == PaymentTypes.LNURLPay)
|
||||
model.PaymentMethodCriteria.Remove(newCriteria);
|
||||
}
|
||||
blob.PaymentMethodCriteria ??= new List<PaymentMethodCriteria>();
|
||||
foreach (var newCriteria in model.PaymentMethodCriteria)
|
||||
{
|
||||
var paymentMethodId = PaymentMethodId.Parse(newCriteria.PaymentMethod);
|
||||
var existingCriteria = blob.PaymentMethodCriteria.FirstOrDefault(c => c.PaymentMethod == paymentMethodId);
|
||||
if (existingCriteria != null)
|
||||
blob.PaymentMethodCriteria.Remove(existingCriteria);
|
||||
CurrencyValue.TryParse(newCriteria.Value, out var cv);
|
||||
blob.PaymentMethodCriteria.Add(new PaymentMethodCriteria()
|
||||
{
|
||||
CurrencyValue.TryParse(viewModel.Value, out var cv);
|
||||
return new PaymentMethodCriteria() { Above = viewModel.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan, Value = cv, PaymentMethod = PaymentMethodId.Parse(viewModel.PaymentMethod) };
|
||||
}).ToList();
|
||||
|
||||
Above = newCriteria.Type == PaymentMethodCriteriaViewModel.CriteriaType.GreaterThan,
|
||||
Value = cv,
|
||||
PaymentMethod = paymentMethodId
|
||||
});
|
||||
}
|
||||
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
||||
blob.LazyPaymentMethods = model.LazyPaymentMethods;
|
||||
blob.RedirectAutomatically = model.RedirectAutomatically;
|
||||
|
@ -493,8 +520,8 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, StoreViewModel vm)
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob,
|
||||
out List<StoreDerivationScheme> derivationSchemes, out List<StoreLightningNode> lightningNodes)
|
||||
{
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var derivationByCryptoCode =
|
||||
|
@ -506,8 +533,12 @@ namespace BTCPayServer.Controllers
|
|||
var lightningByCryptoCode = store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.Where(method => method.PaymentId.PaymentType == LightningPaymentType.Instance)
|
||||
.ToDictionary(c => c.CryptoCode.ToUpperInvariant());
|
||||
|
||||
derivationSchemes = new List<StoreDerivationScheme>();
|
||||
lightningNodes = new List<StoreLightningNode>();
|
||||
|
||||
foreach (var paymentMethodId in _paymentMethodHandlerDictionary.Distinct().SelectMany(handler => handler.GetSupportedPaymentMethods()))
|
||||
{
|
||||
switch (paymentMethodId.PaymentType)
|
||||
|
@ -517,7 +548,7 @@ namespace BTCPayServer.Controllers
|
|||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||
|
||||
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
||||
derivationSchemes.Add(new StoreDerivationScheme
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
WalletSupported = network.WalletSupported,
|
||||
|
@ -529,10 +560,14 @@ namespace BTCPayServer.Controllers
|
|||
#endif
|
||||
});
|
||||
break;
|
||||
|
||||
case LNURLPayPaymentType lnurlPayPaymentType:
|
||||
break;
|
||||
|
||||
case LightningPaymentType _:
|
||||
var lightning = lightningByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var isEnabled = !excludeFilters.Match(paymentMethodId) && lightning != null;
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode
|
||||
lightningNodes.Add(new StoreLightningNode
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
|
@ -544,30 +579,92 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
[HttpGet("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore()
|
||||
public IActionResult UpdateStore()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new StoreViewModel();
|
||||
vm.Id = store.Id;
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.DefaultCurrency = storeBlob.DefaultCurrency;
|
||||
vm.NetworkFeeMode = storeBlob.NetworkFeeMode;
|
||||
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.CanDelete = _Repo.CanDeleteStores();
|
||||
AddPaymentMethods(store, storeBlob, vm);
|
||||
var vm = new StoreViewModel
|
||||
{
|
||||
Id = store.Id,
|
||||
CanDelete = _Repo.CanDeleteStores(),
|
||||
StoreName = store.StoreName,
|
||||
StoreWebsite = store.StoreWebsite,
|
||||
HintWallet = storeBlob.Hints.Wallet,
|
||||
HintLightning = storeBlob.Hints.Lightning
|
||||
};
|
||||
|
||||
AddPaymentMethods(store, storeBlob,
|
||||
out var derivationSchemes, out var lightningNodes);
|
||||
|
||||
vm.DerivationSchemes = derivationSchemes;
|
||||
vm.LightningNodes = lightningNodes;
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (CurrentStore.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreName = model.StoreName;
|
||||
}
|
||||
if (CurrentStore.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/payment")]
|
||||
public async Task<IActionResult> Payment()
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var vm = new PaymentViewModel
|
||||
{
|
||||
NetworkFeeMode = storeBlob.NetworkFeeMode,
|
||||
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
|
||||
SpeedPolicy = store.SpeedPolicy,
|
||||
PaymentTolerance = storeBlob.PaymentTolerance,
|
||||
DefaultCurrency = storeBlob.DefaultCurrency
|
||||
};
|
||||
|
||||
AddPaymentMethods(store, storeBlob,
|
||||
out var derivationSchemes, out var lightningNodes);
|
||||
|
||||
vm.DerivationSchemes = derivationSchemes;
|
||||
vm.LightningNodes = lightningNodes;
|
||||
vm.MonitoringExpiration = (int)storeBlob.MonitoringExpiration.TotalMinutes;
|
||||
vm.InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes;
|
||||
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
||||
vm.PaymentTolerance = storeBlob.PaymentTolerance;
|
||||
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
||||
vm.HintWallet = storeBlob.Hints.Wallet;
|
||||
vm.HintLightning = storeBlob.Hints.Lightning;
|
||||
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
vm.LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints;
|
||||
vm.OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback;
|
||||
|
@ -581,12 +678,12 @@ namespace BTCPayServer.Controllers
|
|||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet);
|
||||
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||
[HttpPost("{storeId}/payment")]
|
||||
public async Task<IActionResult> Payment(PaymentViewModel model, string command = null)
|
||||
{
|
||||
bool needUpdate = false;
|
||||
if (CurrentStore.SpeedPolicy != model.SpeedPolicy)
|
||||
|
@ -594,16 +691,6 @@ namespace BTCPayServer.Controllers
|
|||
needUpdate = true;
|
||||
CurrentStore.SpeedPolicy = model.SpeedPolicy;
|
||||
}
|
||||
if (CurrentStore.StoreName != model.StoreName)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreName = model.StoreName;
|
||||
}
|
||||
if (CurrentStore.StoreWebsite != model.StoreWebsite)
|
||||
{
|
||||
needUpdate = true;
|
||||
CurrentStore.StoreWebsite = model.StoreWebsite;
|
||||
}
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
blob.DefaultCurrency = model.DefaultCurrency;
|
||||
|
@ -630,7 +717,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Payment settings successfully updated";
|
||||
|
||||
if (payjoinChanged && blob.PayJoinEnabled)
|
||||
{
|
||||
|
@ -646,13 +733,13 @@ namespace BTCPayServer.Controllers
|
|||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = $"The store was updated successfully. However, payjoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
Html = $"The payment settings were updated successfully. However, payjoin will not work for {string.Join(", ", problematicPayjoinEnabledMethods)} until you configure them to be a <a href='https://docs.btcpayserver.org/HotWallet/' class='alert-link' target='_blank'>hot wallet</a>."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new
|
||||
return RedirectToAction(nameof(Payment), new
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
|
|
|
@ -35,6 +35,7 @@ using NBitpayClient;
|
|||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using BTCPayServer.Common;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Configuration;
|
||||
|
@ -329,6 +330,8 @@ namespace BTCPayServer.Hosting
|
|||
|
||||
services.AddSingleton<LightningLikePaymentHandler>();
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
|
||||
services.AddSingleton<LNURLPayPaymentHandler>();
|
||||
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LNURLPayPaymentHandler>());
|
||||
services.AddSingleton<IHostedService, LightningListener>();
|
||||
|
||||
services.AddSingleton<PaymentMethodHandlerDictionary>();
|
||||
|
|
203
BTCPayServer/LNURL/LNURLController.cs
Normal file
203
BTCPayServer/LNURL/LNURLController.cs
Normal file
|
@ -0,0 +1,203 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using LNURL;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
[Route("~/{cryptoCode}/[controller]/")]
|
||||
public class LNURLController : Controller
|
||||
{
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly InvoiceController _invoiceController;
|
||||
|
||||
public LNURLController(InvoiceRepository invoiceRepository,
|
||||
EventAggregator eventAggregator,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
LightningLikePaymentHandler lightningLikePaymentHandler,
|
||||
StoreRepository storeRepository,
|
||||
AppService appService,
|
||||
InvoiceController invoiceController)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||
_storeRepository = storeRepository;
|
||||
_appService = appService;
|
||||
_invoiceController = invoiceController;
|
||||
}
|
||||
|
||||
[HttpGet("pay/i/{invoiceId}")]
|
||||
public async Task<IActionResult> GetLNURLForInvoice(string invoiceId, string cryptoCode,
|
||||
[FromQuery] long? amount = null, string comment = null)
|
||||
{
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null || !network.SupportLightning)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var pmi = new PaymentMethodId(cryptoCode, PaymentTypes.LNURLPay);
|
||||
var i = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (i.Status == InvoiceStatusLegacy.New)
|
||||
{
|
||||
var isTopup = i.IsUnsetTopUp();
|
||||
var lnurlSupportedPaymentMethod =
|
||||
i.GetSupportedPaymentMethod<LNURLPaySupportedPaymentMethod>(pmi).FirstOrDefault();
|
||||
if (lnurlSupportedPaymentMethod is null ||
|
||||
(!isTopup && !lnurlSupportedPaymentMethod.EnableForStandardInvoices))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var lightningPaymentMethod = i.GetPaymentMethod(pmi);
|
||||
var accounting = lightningPaymentMethod.Calculate();
|
||||
var paymentMethodDetails =
|
||||
lightningPaymentMethod.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
|
||||
if (paymentMethodDetails.LightningSupportedPaymentMethod is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var min = new LightMoney(isTopup ? 1m : accounting.Due.ToUnit(MoneyUnit.Satoshi),
|
||||
LightMoneyUnit.Satoshi);
|
||||
var max = isTopup ? LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC) : min;
|
||||
|
||||
List<string[]> lnurlMetadata = new List<string[]>();
|
||||
|
||||
lnurlMetadata.Add(new[] { "text/plain", i.Id });
|
||||
|
||||
var metadata = JsonConvert.SerializeObject(lnurlMetadata);
|
||||
if (amount.HasValue && (amount < min || amount > max))
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR", Reason = "Amount is out of bounds."
|
||||
});
|
||||
}
|
||||
|
||||
if (amount.HasValue && string.IsNullOrEmpty(paymentMethodDetails.BOLT11) ||
|
||||
paymentMethodDetails.GeneratedBoltAmount != amount)
|
||||
{
|
||||
var client =
|
||||
_lightningLikePaymentHandler.CreateLightningClient(
|
||||
paymentMethodDetails.LightningSupportedPaymentMethod, network);
|
||||
if (!string.IsNullOrEmpty(paymentMethodDetails.BOLT11))
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.CancelInvoice(paymentMethodDetails.InvoiceId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//not a fully supported option
|
||||
}
|
||||
}
|
||||
|
||||
var descriptionHash = new uint256(Hashes.SHA256(Encoding.UTF8.GetBytes(metadata)));
|
||||
LightningInvoice invoice;
|
||||
try
|
||||
{
|
||||
invoice = await client.CreateInvoice(new CreateInvoiceParams(amount.Value,
|
||||
descriptionHash,
|
||||
i.ExpirationTime.ToUniversalTime() - DateTimeOffset.UtcNow));
|
||||
if (!BOLT11PaymentRequest.Parse(invoice.BOLT11, network.NBitcoinNetwork)
|
||||
.VerifyDescriptionHash(metadata))
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR",
|
||||
Reason = "Lightning node could not generate invoice with a VALID description hash"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR",
|
||||
Reason = "Lightning node could not generate invoice with description hash"
|
||||
});
|
||||
}
|
||||
|
||||
paymentMethodDetails.BOLT11 = invoice.BOLT11;
|
||||
paymentMethodDetails.InvoiceId = invoice.Id;
|
||||
paymentMethodDetails.GeneratedBoltAmount = new LightMoney(amount.Value);
|
||||
if (lnurlSupportedPaymentMethod.LUD12Enabled)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
}
|
||||
|
||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||
|
||||
_eventAggregator.Publish(new InvoiceNewPaymentDetailsEvent(invoiceId,
|
||||
paymentMethodDetails, pmi));
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true, Routes = Array.Empty<string>(), Pr = paymentMethodDetails.BOLT11
|
||||
});
|
||||
}
|
||||
|
||||
if (amount.HasValue && paymentMethodDetails.GeneratedBoltAmount == amount)
|
||||
{
|
||||
if (lnurlSupportedPaymentMethod.LUD12Enabled && paymentMethodDetails.ProvidedComment != comment)
|
||||
{
|
||||
paymentMethodDetails.ProvidedComment = comment;
|
||||
lightningPaymentMethod.SetPaymentMethodDetails(paymentMethodDetails);
|
||||
await _invoiceRepository.UpdateInvoicePaymentMethod(invoiceId, lightningPaymentMethod);
|
||||
}
|
||||
|
||||
return Ok(new LNURLPayRequest.LNURLPayRequestCallbackResponse
|
||||
{
|
||||
Disposable = true, Routes = Array.Empty<string>(), Pr = paymentMethodDetails.BOLT11
|
||||
});
|
||||
}
|
||||
|
||||
if (amount is null)
|
||||
{
|
||||
return Ok(new LNURLPayRequest
|
||||
{
|
||||
Tag = "payRequest",
|
||||
MinSendable = min,
|
||||
MaxSendable = max,
|
||||
CommentAllowed = lnurlSupportedPaymentMethod.LUD12Enabled ? 2000 : 0,
|
||||
Metadata = metadata,
|
||||
Callback = new Uri(Request.GetCurrentUrl())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return BadRequest(new LNUrlStatusResponse
|
||||
{
|
||||
Status = "ERROR", Reason = "Invoice not in a valid payable state"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
@ -79,7 +80,7 @@ namespace BTCPayServer.Models
|
|||
}
|
||||
|
||||
[JsonProperty("cryptoInfo")]
|
||||
public List<NBitpayClient.InvoiceCryptoInfo> CryptoInfo { get; set; }
|
||||
public List<InvoiceCryptoInfo> CryptoInfo { get; set; }
|
||||
|
||||
//"price":5
|
||||
[JsonProperty("price")]
|
||||
|
@ -262,7 +263,7 @@ namespace BTCPayServer.Models
|
|||
[JsonProperty("addresses")]
|
||||
public Dictionary<string, string> Addresses { get; set; }
|
||||
[JsonProperty("paymentCodes")]
|
||||
public Dictionary<string, NBitpayClient.InvoicePaymentUrls> PaymentCodes { get; set; }
|
||||
public Dictionary<string, InvoiceCryptoInfo.InvoicePaymentUrls> PaymentCodes { get; set; }
|
||||
[JsonProperty("buyer")]
|
||||
public JObject Buyer { get; set; }
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
{
|
||||
public string Crypto { get; set; }
|
||||
public string BOLT11 { get; set; }
|
||||
public PaymentType Type { get; set; }
|
||||
}
|
||||
|
||||
public class InvoiceDetailsModel
|
||||
|
@ -45,6 +46,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
public string Overpaid { get; set; }
|
||||
[JsonIgnore]
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
|
||||
public PaymentMethod PaymentMethodRaw { get; set; }
|
||||
}
|
||||
public class AddressModel
|
||||
{
|
||||
|
|
|
@ -10,7 +10,22 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
|
||||
public class LightningNodeViewModel
|
||||
{
|
||||
[Display(Name = "Enable LNURL")]
|
||||
public bool LNURLEnabled { get; set; }
|
||||
|
||||
[Display(Name = "LNURL Classic Mode")]
|
||||
public bool LNURLBech32Mode { get; set; } = true;
|
||||
|
||||
[Display(Name = "LNURL enabled for standard invoices")]
|
||||
public bool LNURLStandardInvoiceEnabled { get; set; }
|
||||
|
||||
[Display(Name = "Allow payee to pass a comment")]
|
||||
public bool LUD12Enabled { get; set; }
|
||||
|
||||
[Display(Name = "Do not offer BOLT11 for standard invoices")]
|
||||
public bool DisableBolt11PaymentMethod { get; set; }
|
||||
public LightningNodeType LightningNodeType { get; set; }
|
||||
|
||||
[Display(Name = "Connection string")]
|
||||
public string ConnectionString { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
|
|
64
BTCPayServer/Models/StoreViewModels/PaymentViewModel.cs
Normal file
64
BTCPayServer/Models/StoreViewModels/PaymentViewModel.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using static BTCPayServer.Data.StoreBlob;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class PaymentViewModel
|
||||
{
|
||||
public List<StoreDerivationScheme> DerivationSchemes { get; set; }
|
||||
public List<StoreLightningNode> LightningNodes { get; set; }
|
||||
public bool IsOnchainSetup { get; set; }
|
||||
public bool IsLightningSetup { get; set; }
|
||||
public bool CanUsePayJoin { get; set; }
|
||||
|
||||
[Display(Name = "Allow anyone to create invoice")]
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
[Display(Name = "Invoice expires if the full amount has not been paid after …")]
|
||||
[Range(1, 60 * 24 * 24)]
|
||||
public int InvoiceExpiration { get; set; }
|
||||
|
||||
[Display(Name = "Payment invalid if transactions fails to confirm … after invoice expiration")]
|
||||
[Range(10, 60 * 24 * 24)]
|
||||
public int MonitoringExpiration { get; set; }
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction …")]
|
||||
public SpeedPolicy SpeedPolicy { get; set; }
|
||||
|
||||
[Display(Name = "Add additional fee (network fee) to invoice …")]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; }
|
||||
|
||||
[Display(Name = "Description template of the lightning invoice")]
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
|
||||
[Display(Name = "Enable Payjoin/P2EP")]
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
|
||||
[Display(Name = "Show recommended fee")]
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
[Display(Name = "Recommended fee confirmation target blocks")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
[Display(Name = "Display Lightning payment amounts in Satoshis")]
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
[Display(Name = "Add hop hints for private channels to the Lightning invoice")]
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
[Display(Name = "Include Lightning invoice fallback to on-chain BIP21 payment URL")]
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
|
||||
[Display(Name = "Consider the invoice paid even if the paid amount is ... % less than expected")]
|
||||
[Range(0, 100)]
|
||||
public double PaymentTolerance { get; set; }
|
||||
|
||||
[Display(Name = "Default currency")]
|
||||
[MaxLength(10)]
|
||||
public string DefaultCurrency { get; set; }
|
||||
}
|
||||
}
|
12
BTCPayServer/Models/StoreViewModels/StoreDerivationScheme.cs
Normal file
12
BTCPayServer/Models/StoreViewModels/StoreDerivationScheme.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoreDerivationScheme
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool WalletSupported { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Collapsed { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public class StoreLightningNode
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,130 +8,24 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
{
|
||||
public class StoreViewModel
|
||||
{
|
||||
public class DerivationScheme
|
||||
{
|
||||
public string Crypto { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool WalletSupported { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Collapsed { get; set; }
|
||||
}
|
||||
|
||||
public class AdditionalPaymentMethod
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public string Action { get; set; }
|
||||
}
|
||||
public StoreViewModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public List<StoreDerivationScheme> DerivationSchemes { get; set; }
|
||||
public List<StoreLightningNode> LightningNodes { get; set; }
|
||||
public bool HintWallet { get; set; }
|
||||
public bool HintLightning { get; set; }
|
||||
public bool CanDelete { get; set; }
|
||||
|
||||
[Display(Name = "Store ID")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[Display(Name = "Store Name")]
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[MinLength(1)]
|
||||
public string StoreName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string StoreName { get; set; }
|
||||
|
||||
[Uri]
|
||||
[Display(Name = "Store Website")]
|
||||
[MaxLength(500)]
|
||||
public string StoreWebsite
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Default currency")]
|
||||
[MaxLength(10)]
|
||||
public string DefaultCurrency { get; set; }
|
||||
|
||||
[Display(Name = "Allow anyone to create invoice")]
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
|
||||
|
||||
[Display(Name = "Invoice expires if the full amount has not been paid after …")]
|
||||
[Range(1, 60 * 24 * 24)]
|
||||
public int InvoiceExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Payment invalid if transactions fails to confirm … after invoice expiration")]
|
||||
[Range(10, 60 * 24 * 24)]
|
||||
public int MonitoringExpiration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Display(Name = "Consider the invoice confirmed when the payment transaction …")]
|
||||
public SpeedPolicy SpeedPolicy
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Add additional fee (network fee) to invoice …")]
|
||||
public NetworkFeeMode NetworkFeeMode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Display(Name = "Description template of the lightning invoice")]
|
||||
public string LightningDescriptionTemplate { get; set; }
|
||||
|
||||
[Display(Name = "Enable Payjoin/P2EP")]
|
||||
public bool PayJoinEnabled { get; set; }
|
||||
public bool CanUsePayJoin { get; set; }
|
||||
public bool IsOnchainSetup { get; set; }
|
||||
public bool IsLightningSetup { get; set; }
|
||||
|
||||
public bool HintWallet { get; set; }
|
||||
public bool HintLightning { get; set; }
|
||||
|
||||
[Display(Name = "Show recommended fee")]
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
|
||||
[Display(Name = "Recommended fee confirmation target blocks")]
|
||||
[Range(1, double.PositiveInfinity)]
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
||||
[Display(Name = "Display Lightning payment amounts in Satoshis")]
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
|
||||
[Display(Name = "Add hop hints for private channels to the Lightning invoice")]
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
|
||||
[Display(Name = "Include Lightning invoice fallback to on-chain BIP21 payment URL")]
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
|
||||
public class LightningNode
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
public List<LightningNode> LightningNodes
|
||||
{
|
||||
get; set;
|
||||
} = new List<LightningNode>();
|
||||
|
||||
[Display(Name = "Consider the invoice paid even if the paid amount is ... % less than expected")]
|
||||
[Range(0, 100)]
|
||||
public double PaymentTolerance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string StoreWebsite { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,5 +18,6 @@ namespace BTCPayServer.Payments
|
|||
decimal GetNextNetworkFee();
|
||||
|
||||
bool Activated {get;set;}
|
||||
virtual string GetAdditionalDataPartialName() => null;
|
||||
}
|
||||
}
|
||||
|
|
170
BTCPayServer/Payments/LNURLPay/LNURLPayPaymentHandler.cs
Normal file
170
BTCPayServer/Payments/LNURLPay/LNURLPayPaymentHandler.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class LNURLPayPaymentHandler : PaymentMethodHandlerBase<LNURLPaySupportedPaymentMethod, BTCPayNetwork>
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _networkProvider;
|
||||
private readonly CurrencyNameTable _currencyNameTable;
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
|
||||
public LNURLPayPaymentHandler(
|
||||
BTCPayNetworkProvider networkProvider,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
IOptions<LightningNetworkOptions> options,
|
||||
LightningLikePaymentHandler lightningLikePaymentHandler)
|
||||
{
|
||||
_networkProvider = networkProvider;
|
||||
_currencyNameTable = currencyNameTable;
|
||||
_lightningLikePaymentHandler = lightningLikePaymentHandler;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public override PaymentType PaymentType => PaymentTypes.LightningLike;
|
||||
|
||||
public IOptions<LightningNetworkOptions> Options { get; }
|
||||
|
||||
public override async Task<IPaymentMethodDetails> CreatePaymentMethodDetails(
|
||||
InvoiceLogs logs,
|
||||
LNURLPaySupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, Data.StoreData store,
|
||||
BTCPayNetwork network, object preparePaymentObject)
|
||||
{
|
||||
if (!supportedPaymentMethod.EnableForStandardInvoices &&
|
||||
paymentMethod.ParentEntity.Type == InvoiceType.Standard)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("LNURL is not enabled for standard invoices");
|
||||
}
|
||||
if (string.IsNullOrEmpty(paymentMethod.ParentEntity.Id))
|
||||
{
|
||||
var lnSupported = store.GetSupportedPaymentMethods(_networkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>().SingleOrDefault(method =>
|
||||
method.PaymentId.CryptoCode == supportedPaymentMethod.CryptoCode &&
|
||||
method.PaymentId.PaymentType == LightningPaymentType.Instance);
|
||||
|
||||
if (lnSupported is null)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("LNURL requires a lightning node to be configured for the store.");
|
||||
}
|
||||
|
||||
return new LNURLPayPaymentMethodDetails()
|
||||
{
|
||||
Activated = false, LightningSupportedPaymentMethod = lnSupported
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var lnLightningSupportedPaymentMethod =
|
||||
((LNURLPayPaymentMethodDetails)paymentMethod.GetPaymentMethodDetails()).LightningSupportedPaymentMethod;
|
||||
|
||||
NodeInfo? nodeInfo = null;
|
||||
if (lnLightningSupportedPaymentMethod != null)
|
||||
{
|
||||
nodeInfo = (await _lightningLikePaymentHandler.GetNodeInfo(lnLightningSupportedPaymentMethod, _networkProvider.GetNetwork<BTCPayNetwork>(supportedPaymentMethod.CryptoCode), logs, paymentMethod.PreferOnion)).FirstOrDefault();
|
||||
}
|
||||
|
||||
return new LNURLPayPaymentMethodDetails
|
||||
{
|
||||
Activated = true,
|
||||
LightningSupportedPaymentMethod = lnLightningSupportedPaymentMethod,
|
||||
BTCPayInvoiceId = paymentMethod.ParentEntity.Id,
|
||||
Bech32Mode = supportedPaymentMethod.UseBech32Scheme,
|
||||
NodeInfo = nodeInfo?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<PaymentMethodId> GetSupportedPaymentMethods()
|
||||
{
|
||||
return _networkProvider
|
||||
.GetAll()
|
||||
.OfType<BTCPayNetwork>()
|
||||
.Where(network => network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning)
|
||||
.Select(network => new PaymentMethodId(network.CryptoCode, PaymentTypes.LNURLPay));
|
||||
}
|
||||
|
||||
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,
|
||||
StoreBlob storeBlob, IPaymentMethod paymentMethod)
|
||||
{
|
||||
var paymentMethodId = paymentMethod.GetId();
|
||||
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(model.CryptoCode);
|
||||
model.PaymentMethodName = GetPaymentMethodName(network);
|
||||
model.InvoiceBitcoinUrl = cryptoInfo.PaymentUrls?.AdditionalData["LNURLP"].ToObject<string>();
|
||||
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
|
||||
model.BtcAddress = model.InvoiceBitcoinUrl;
|
||||
model.PeerInfo = ((LNURLPayPaymentMethodDetails) paymentMethod.GetPaymentMethodDetails()).NodeInfo;
|
||||
if ( storeBlob.LightningAmountInSatoshi && model.CryptoCode == "BTC")
|
||||
{
|
||||
var satoshiCulture = new CultureInfo(CultureInfo.InvariantCulture.Name);
|
||||
satoshiCulture.NumberFormat.NumberGroupSeparator = " ";
|
||||
model.CryptoCode = "Sats";
|
||||
model.BtcDue = Money.Parse(model.BtcDue).ToUnit(MoneyUnit.Satoshi).ToString("N0", satoshiCulture);
|
||||
model.BtcPaid = Money.Parse(model.BtcPaid).ToUnit(MoneyUnit.Satoshi).ToString("N0", satoshiCulture);
|
||||
model.OrderAmount = Money.Parse(model.OrderAmount).ToUnit(MoneyUnit.Satoshi)
|
||||
.ToString("N0", satoshiCulture);
|
||||
model.NetworkFee = new Money(model.NetworkFee, MoneyUnit.BTC).ToUnit(MoneyUnit.Satoshi);
|
||||
model.Rate =
|
||||
_currencyNameTable.DisplayFormatCurrency(paymentMethod.Rate / 100_000_000, model.InvoiceCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetCryptoImage(network);
|
||||
}
|
||||
|
||||
private string GetCryptoImage(BTCPayNetworkBase network)
|
||||
{
|
||||
return ((BTCPayNetwork)network).LightningImagePath;
|
||||
}
|
||||
|
||||
public override string GetPaymentMethodName(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
var network = _networkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
return GetPaymentMethodName(network);
|
||||
}
|
||||
|
||||
public override CheckoutUIPaymentMethodSettings GetCheckoutUISettings()
|
||||
{
|
||||
return new CheckoutUIPaymentMethodSettings()
|
||||
{
|
||||
ExtensionPartial = "Lightning/LightningLikeMethodCheckout",
|
||||
CheckoutBodyVueComponentName = "LightningLikeMethodCheckout",
|
||||
CheckoutHeaderVueComponentName = "LightningLikeMethodCheckoutHeader",
|
||||
NoScriptPartialName = "Lightning/LightningLikeMethodCheckoutNoScript"
|
||||
};
|
||||
}
|
||||
|
||||
private string GetPaymentMethodName(BTCPayNetworkBase network)
|
||||
{
|
||||
return $"{network.DisplayName} (Lightning LNURL)";
|
||||
}
|
||||
|
||||
public override object PreparePayment(LNURLPaySupportedPaymentMethod supportedPaymentMethod,
|
||||
Data.StoreData store,
|
||||
BTCPayNetworkBase network)
|
||||
{
|
||||
// pass a non null obj, so that if lazy payment feature is used, it has a marker to trigger activation
|
||||
return new { };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public class LNURLPayPaymentMethodDetails : LightningLikePaymentMethodDetails
|
||||
{
|
||||
public LightningSupportedPaymentMethod LightningSupportedPaymentMethod { get; set; }
|
||||
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney GeneratedBoltAmount { get; set; }
|
||||
public string BTCPayInvoiceId { get; set; }
|
||||
public bool Bech32Mode { get; set; }
|
||||
|
||||
public string ProvidedComment { get; set; }
|
||||
|
||||
public override PaymentType GetPaymentType()
|
||||
{
|
||||
return LNURLPayPaymentType.Instance;
|
||||
}
|
||||
|
||||
public override string GetAdditionalDataPartialName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(ProvidedComment))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return "LNURL/AdditionalPaymentMethodDetails";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Payments.Lightning
|
||||
{
|
||||
public class LNURLPaySupportedPaymentMethod : ISupportedPaymentMethod
|
||||
{
|
||||
public string CryptoCode { get; set; } = string.Empty;
|
||||
|
||||
[JsonIgnore]
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LNURLPay);
|
||||
|
||||
public bool UseBech32Scheme { get; set; }
|
||||
|
||||
public bool EnableForStandardInvoices { get; set; } = false;
|
||||
|
||||
public bool LUD12Enabled { get; set; } = true;
|
||||
|
||||
}
|
||||
}
|
73
BTCPayServer/Payments/LNURLPay/PaymentTypes.LNURL.cs
Normal file
73
BTCPayServer/Payments/LNURLPay/PaymentTypes.LNURL.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers.GreenField;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public class LNURLPayPaymentType : LightningPaymentType
|
||||
{
|
||||
public new static LNURLPayPaymentType Instance { get; } = new LNURLPayPaymentType();
|
||||
public override string ToPrettyString() => "LNURL-Pay";
|
||||
public override string GetId() => "LNURLPAY";
|
||||
public override string ToStringNormalized() => "LNURLPAY";
|
||||
public override IPaymentMethodDetails DeserializePaymentMethodDetails(BTCPayNetworkBase network, string str)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LNURLPayPaymentMethodDetails>(str);
|
||||
}
|
||||
|
||||
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network,
|
||||
JToken value)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<LNURLPaySupportedPaymentMethod>(value.ToString());
|
||||
}
|
||||
|
||||
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails,
|
||||
Money cryptoInfoDue, string serverUri)
|
||||
{
|
||||
if (!paymentMethodDetails.Activated)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var lnurlPaymentMethodDetails = (LNURLPayPaymentMethodDetails)paymentMethodDetails;
|
||||
var uri = new Uri(
|
||||
$"{serverUri.WithTrailingSlash()}{network.CryptoCode}/lnurl/pay/i/{lnurlPaymentMethodDetails.BTCPayInvoiceId}");
|
||||
return LNURL.LNURL.EncodeUri(uri, "payRequest", lnurlPaymentMethodDetails.Bech32Mode).ToString();
|
||||
}
|
||||
|
||||
public override string InvoiceViewPaymentPartialName { get; } = "Lightning/ViewLightningLikePaymentData";
|
||||
public override object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore)
|
||||
{
|
||||
if (supportedPaymentMethod is LNURLPaySupportedPaymentMethod lightningSupportedPaymentMethod)
|
||||
return new LNURLPayPaymentMethodBaseData()
|
||||
{
|
||||
UseBech32Scheme = lightningSupportedPaymentMethod.UseBech32Scheme,
|
||||
EnableForStandardInvoices = lightningSupportedPaymentMethod.EnableForStandardInvoices,
|
||||
LUD12Enabled = lightningSupportedPaymentMethod.LUD12Enabled
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool IsPaymentType(string paymentType)
|
||||
{
|
||||
return IsPaymentTypeBase(paymentType);
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
invoiceCryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
||||
{
|
||||
AdditionalData = new Dictionary<string, JToken>()
|
||||
{
|
||||
{"LNURLP", JToken.FromObject(GetPaymentLink(details.Network, details.GetPaymentMethodDetails(), invoiceCryptoInfo.Due,
|
||||
serverUrl))}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
public string BOLT11 { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
|
||||
public uint256 PaymentHash { get; set; }
|
||||
public string PaymentType { get; set; }
|
||||
|
||||
public string GetDestination()
|
||||
{
|
||||
|
@ -33,7 +34,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
|
||||
public PaymentType GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.LightningLike;
|
||||
return string.IsNullOrEmpty(PaymentType) ? PaymentTypes.LightningLike : PaymentTypes.Parse(PaymentType);
|
||||
}
|
||||
|
||||
public string[] GetSearchTerms()
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
}
|
||||
//direct casting to (BTCPayNetwork) is fixed in other pull requests with better generic interfacing for handlers
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var test = GetNodeInfo(supportedPaymentMethod, network, paymentMethod.PreferOnion);
|
||||
var nodeInfo = GetNodeInfo(supportedPaymentMethod, network, logs, paymentMethod.PreferOnion);
|
||||
|
||||
var invoice = paymentMethod.ParentEntity;
|
||||
decimal due = Extensions.RoundUp(invoice.Price / paymentMethod.Rate, network.Divisibility);
|
||||
|
@ -109,56 +109,80 @@ namespace BTCPayServer.Payments.Lightning
|
|||
}
|
||||
}
|
||||
|
||||
var nodeInfo = await test;
|
||||
return new LightningLikePaymentMethodDetails
|
||||
{
|
||||
Activated = true,
|
||||
BOLT11 = lightningInvoice.BOLT11,
|
||||
PaymentHash = BOLT11PaymentRequest.Parse(lightningInvoice.BOLT11, network.NBitcoinNetwork).PaymentHash,
|
||||
InvoiceId = lightningInvoice.Id,
|
||||
NodeInfo = nodeInfo.First().ToString()
|
||||
NodeInfo = (await nodeInfo).FirstOrDefault()?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<NodeInfo[]> GetNodeInfo(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, bool? preferOnion = null)
|
||||
public async Task<NodeInfo[]> GetNodeInfo(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceLogs invoiceLogs, bool? preferOnion = null)
|
||||
{
|
||||
if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||
throw new PaymentMethodUnavailableException("Full node not available");
|
||||
|
||||
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
|
||||
try
|
||||
{
|
||||
var client = supportedPaymentMethod.CreateLightningClient(network, Options.Value, _lightningClientFactory);
|
||||
LightningNodeInformation info;
|
||||
try
|
||||
using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT))
|
||||
{
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
|
||||
(!string.IsNullOrEmpty(ex.InnerException?.Message) ? $" ({ex.InnerException.Message})" : ""));
|
||||
}
|
||||
var client = CreateLightningClient(supportedPaymentMethod, network);
|
||||
LightningNodeInformation info;
|
||||
try
|
||||
{
|
||||
info = await client.GetInfo(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (cts.IsCancellationRequested)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("The lightning node did not reply in a timely manner");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"Error while connecting to the API: {ex.Message}" +
|
||||
(!string.IsNullOrEmpty(ex.InnerException?.Message) ? $" ({ex.InnerException.Message})" : ""));
|
||||
}
|
||||
|
||||
var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
|
||||
? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
|
||||
: info.NodeInfoList.Select(i => i).ToArray();
|
||||
var nodeInfo = preferOnion != null && info.NodeInfoList.Any(i => i.IsTor == preferOnion)
|
||||
? info.NodeInfoList.Where(i => i.IsTor == preferOnion.Value).ToArray()
|
||||
: info.NodeInfoList.Select(i => i).ToArray();
|
||||
|
||||
if (!nodeInfo.Any())
|
||||
{
|
||||
throw new PaymentMethodUnavailableException("No lightning node public address has been configured");
|
||||
}
|
||||
// Maybe the user does not have an easily accessible ln node. Node info should be optional. The UI also supports this.
|
||||
// if (!nodeInfo.Any())
|
||||
// {
|
||||
// throw new PaymentMethodUnavailableException("No lightning node public address has been configured");
|
||||
// }
|
||||
|
||||
var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
|
||||
if (blocksGap > 10)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
|
||||
}
|
||||
var blocksGap = summary.Status.ChainHeight - info.BlockHeight;
|
||||
if (blocksGap > 10)
|
||||
{
|
||||
throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)");
|
||||
}
|
||||
|
||||
return nodeInfo;
|
||||
return nodeInfo;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
invoiceLogs.Write($"NodeInfo failed to be fetched: {e.Message}", InvoiceEventData.EventSeverity.Error);
|
||||
}
|
||||
|
||||
return Array.Empty<NodeInfo>();
|
||||
}
|
||||
|
||||
public ILightningClient CreateLightningClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
|
||||
{
|
||||
var external = supportedPaymentMethod.GetExternalLightningUrl();
|
||||
if (external != null)
|
||||
{
|
||||
return _lightningClientFactory.Create(external, network);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Options.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode, out var connectionString))
|
||||
throw new PaymentMethodUnavailableException("No internal node configured");
|
||||
return _lightningClientFactory.Create(connectionString, network);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
public string InvoiceId { get; set; }
|
||||
public string NodeInfo { get; set; }
|
||||
|
||||
public string GetPaymentDestination()
|
||||
public virtual string GetPaymentDestination()
|
||||
{
|
||||
return BOLT11;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
return PaymentHash ?? BOLT11PaymentRequest.Parse(BOLT11, network).PaymentHash;
|
||||
}
|
||||
|
||||
public PaymentType GetPaymentType()
|
||||
public virtual PaymentType GetPaymentType()
|
||||
{
|
||||
return PaymentTypes.LightningLike;
|
||||
}
|
||||
|
@ -35,5 +35,10 @@ namespace BTCPayServer.Payments.Lightning
|
|||
return 0.0m;
|
||||
}
|
||||
public bool Activated { get; set; }
|
||||
|
||||
public virtual string GetAdditionalDataPartialName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,21 +104,42 @@ namespace BTCPayServer.Payments.Lightning
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetCacheKey(string invoiceId)
|
||||
{
|
||||
return $"{nameof(GetListenedInvoices)}-{invoiceId}";
|
||||
}
|
||||
private Task<List<ListenedInvoice>> GetListenedInvoices(string invoiceId)
|
||||
{
|
||||
return _memoryCache.GetOrCreateAsync($"{nameof(GetListenedInvoices)}-{invoiceId}", async (cacheEntry) =>
|
||||
return _memoryCache.GetOrCreateAsync( GetCacheKey(invoiceId), async (cacheEntry) =>
|
||||
{
|
||||
var listenedInvoices = new List<ListenedInvoice>();
|
||||
var invoice = await _InvoiceRepository.GetInvoice(invoiceId);
|
||||
foreach (var paymentMethod in invoice.GetPaymentMethods()
|
||||
.Where(c => c.GetId().PaymentType == PaymentTypes.LightningLike))
|
||||
.Where(c => new []{PaymentTypes.LightningLike, LNURLPayPaymentType.Instance }.Contains(c.GetId().PaymentType)))
|
||||
{
|
||||
var lightningMethod = paymentMethod.GetPaymentMethodDetails() as LightningLikePaymentMethodDetails;
|
||||
if (lightningMethod == null || !lightningMethod.Activated)
|
||||
continue;
|
||||
var lightningSupportedMethod = invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(c => c.CryptoCode == paymentMethod.GetId().CryptoCode);
|
||||
if (lightningSupportedMethod == null)
|
||||
LightningLikePaymentMethodDetails lightningMethod;
|
||||
LightningSupportedPaymentMethod lightningSupportedMethod;
|
||||
switch (paymentMethod.GetPaymentMethodDetails())
|
||||
{
|
||||
case LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails:
|
||||
|
||||
lightningMethod = lnurlPayPaymentMethodDetails;
|
||||
|
||||
lightningSupportedMethod = lnurlPayPaymentMethodDetails.LightningSupportedPaymentMethod;
|
||||
|
||||
break;
|
||||
case LightningLikePaymentMethodDetails { Activated: true } lightningLikePaymentMethodDetails:
|
||||
lightningMethod = lightningLikePaymentMethodDetails;
|
||||
lightningSupportedMethod = invoice.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(c => c.CryptoCode == paymentMethod.GetId().CryptoCode);
|
||||
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lightningSupportedMethod == null || string.IsNullOrEmpty(lightningMethod.InvoiceId))
|
||||
continue;
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethod.GetId().CryptoCode);
|
||||
|
||||
|
@ -164,7 +185,6 @@ namespace BTCPayServer.Payments.Lightning
|
|||
if (inv.State.Status == InvoiceStatusLegacy.New &&
|
||||
inv.State.ExceptionStatus == InvoiceExceptionStatus.PaidPartial)
|
||||
{
|
||||
|
||||
var invoice = await _InvoiceRepository.GetInvoice(inv.InvoiceId);
|
||||
await CreateNewLNInvoiceForBTCPayInvoice(invoice);
|
||||
}
|
||||
|
@ -174,6 +194,15 @@ namespace BTCPayServer.Payments.Lightning
|
|||
{
|
||||
if (inv.PaymentMethodId.PaymentType == LightningPaymentType.Instance)
|
||||
{
|
||||
_memoryCache.Remove(GetCacheKey(inv.InvoiceId));
|
||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
||||
}
|
||||
}));
|
||||
leases.Add(_Aggregator.Subscribe<Events.InvoiceNewPaymentDetailsEvent>(inv =>
|
||||
{
|
||||
if (inv.PaymentMethodId.PaymentType == LNURLPayPaymentType.Instance)
|
||||
{
|
||||
_memoryCache.Remove(GetCacheKey(inv.InvoiceId));
|
||||
_CheckInvoices.Writer.TryWrite(inv.InvoiceId);
|
||||
}
|
||||
}));
|
||||
|
@ -196,7 +225,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
private async Task CreateNewLNInvoiceForBTCPayInvoice(InvoiceEntity invoice)
|
||||
{
|
||||
var paymentMethods = invoice.GetPaymentMethods()
|
||||
.Where(method => method.GetId().PaymentType == PaymentTypes.LightningLike)
|
||||
.Where(method => new []{PaymentTypes.LightningLike, LNURLPayPaymentType.Instance}.Contains(method.GetId().PaymentType))
|
||||
.ToArray();
|
||||
var store = await _storeRepository.FindStore(invoice.StoreId);
|
||||
if (paymentMethods.Any())
|
||||
|
@ -209,8 +238,60 @@ namespace BTCPayServer.Payments.Lightning
|
|||
{
|
||||
try
|
||||
{
|
||||
var supportedMethod = invoice
|
||||
var oldDetails = (LightningLikePaymentMethodDetails) paymentMethod.GetPaymentMethodDetails();
|
||||
if (!oldDetails.Activated)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (oldDetails is LNURLPayPaymentMethodDetails lnurlPayPaymentMethodDetails && !string.IsNullOrEmpty(lnurlPayPaymentMethodDetails.BOLT11))
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = _lightningLikePaymentHandler.CreateLightningClient(lnurlPayPaymentMethodDetails.LightningSupportedPaymentMethod,
|
||||
(BTCPayNetwork)paymentMethod.Network);
|
||||
await client.CancelInvoice(oldDetails.InvoiceId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//not a fully supported option
|
||||
}
|
||||
|
||||
lnurlPayPaymentMethodDetails = new LNURLPayPaymentMethodDetails()
|
||||
{
|
||||
Activated = lnurlPayPaymentMethodDetails.Activated,
|
||||
Bech32Mode = lnurlPayPaymentMethodDetails.Bech32Mode,
|
||||
InvoiceId = null,
|
||||
NodeInfo = lnurlPayPaymentMethodDetails.NodeInfo,
|
||||
GeneratedBoltAmount = null,
|
||||
BOLT11 = null,
|
||||
LightningSupportedPaymentMethod = lnurlPayPaymentMethodDetails.LightningSupportedPaymentMethod,
|
||||
BTCPayInvoiceId = lnurlPayPaymentMethodDetails.BTCPayInvoiceId
|
||||
};
|
||||
await _InvoiceRepository.NewPaymentDetails(invoice.Id, lnurlPayPaymentMethodDetails,
|
||||
paymentMethod.Network);
|
||||
|
||||
_Aggregator.Publish(new Events.InvoiceNewPaymentDetailsEvent(invoice.Id,
|
||||
lnurlPayPaymentMethodDetails, paymentMethod.GetId()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
LightningSupportedPaymentMethod supportedMethod = invoice
|
||||
.GetSupportedPaymentMethod<LightningSupportedPaymentMethod>(paymentMethod.GetId()).First();
|
||||
|
||||
try
|
||||
{
|
||||
var client = _lightningLikePaymentHandler.CreateLightningClient(supportedMethod,
|
||||
(BTCPayNetwork)paymentMethod.Network);
|
||||
await client.CancelInvoice(oldDetails.InvoiceId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//not a fully supported option
|
||||
}
|
||||
|
||||
var prepObj =
|
||||
_lightningLikePaymentHandler.PreparePayment(supportedMethod, store, paymentMethod.Network);
|
||||
var newPaymentMethodDetails =
|
||||
|
@ -346,7 +427,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
var client = _lightningClientFactory.Create(ConnectionString, _network);
|
||||
LightningInvoice lightningInvoice = await client.GetInvoice(listenedInvoice.PaymentMethodDetails.InvoiceId, cancellation);
|
||||
if (lightningInvoice?.Status is LightningInvoiceStatus.Paid &&
|
||||
await AddPayment(lightningInvoice, listenedInvoice.InvoiceId))
|
||||
await AddPayment(lightningInvoice, listenedInvoice.InvoiceId,listenedInvoice.PaymentMethod.GetId().PaymentType))
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via polling on {listenedInvoice.InvoiceId}");
|
||||
}
|
||||
|
@ -392,7 +473,7 @@ namespace BTCPayServer.Payments.Lightning
|
|||
if (notification.Status == LightningInvoiceStatus.Paid &&
|
||||
notification.PaidAt.HasValue && notification.Amount != null)
|
||||
{
|
||||
if (await AddPayment(notification, listenedInvoice.InvoiceId))
|
||||
if (await AddPayment(notification, listenedInvoice.InvoiceId, listenedInvoice.PaymentMethod.GetId().PaymentType))
|
||||
{
|
||||
Logs.PayServer.LogInformation($"{_network.CryptoCode} (Lightning): Payment detected via notification ({listenedInvoice.InvoiceId})");
|
||||
}
|
||||
|
@ -439,13 +520,14 @@ namespace BTCPayServer.Payments.Lightning
|
|||
bool _ErrorAlreadyLogged = false;
|
||||
readonly ConcurrentDictionary<string, ListenedInvoice> _ListenedInvoices = new ConcurrentDictionary<string, ListenedInvoice>();
|
||||
|
||||
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId)
|
||||
public async Task<bool> AddPayment(LightningInvoice notification, string invoiceId, PaymentType paymentType)
|
||||
{
|
||||
var payment = await _paymentService.AddPayment(invoiceId, notification.PaidAt.Value, new LightningLikePaymentData()
|
||||
{
|
||||
BOLT11 = notification.BOLT11,
|
||||
PaymentHash = BOLT11PaymentRequest.Parse(notification.BOLT11, _network.NBitcoinNetwork).PaymentHash,
|
||||
Amount = notification.AmountReceived ?? notification.Amount, // if running old version amount received might be unavailable
|
||||
Amount = notification.AmountReceived ?? notification.Amount, // if running old version amount received might be unavailable,
|
||||
PaymentType = paymentType.ToString()
|
||||
}, _network, accounted: true);
|
||||
if (payment != null)
|
||||
{
|
||||
|
|
|
@ -2,12 +2,13 @@ using System;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Client.Models;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
|
@ -101,5 +102,14 @@ namespace BTCPayServer.Payments
|
|||
{
|
||||
return string.IsNullOrEmpty(paymentType) || base.IsPaymentType(paymentType);
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo cryptoInfo,
|
||||
string serverUrl)
|
||||
{
|
||||
cryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
||||
{
|
||||
BIP21 = GetPaymentLink(details.Network, details.GetPaymentMethodDetails(), cryptoInfo.Due, serverUrl),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace BTCPayServer.Payments
|
|||
{
|
||||
public static LightningPaymentType Instance { get; } = new LightningPaymentType();
|
||||
|
||||
private LightningPaymentType() { }
|
||||
private protected LightningPaymentType() { }
|
||||
|
||||
public override string ToPrettyString() => "Off-Chain";
|
||||
public override string GetId() => "LightningLike";
|
||||
|
@ -87,5 +87,14 @@ namespace BTCPayServer.Payments
|
|||
{
|
||||
return paymentType?.Equals("offchain", StringComparison.InvariantCultureIgnoreCase) is true || base.IsPaymentType(paymentType);
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
invoiceCryptoInfo.PaymentUrls = new InvoiceCryptoInfo.InvoicePaymentUrls()
|
||||
{
|
||||
BOLT11 = GetPaymentLink(details.Network, details.GetPaymentMethodDetails(), invoiceCryptoInfo.Due,
|
||||
serverUrl)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Payments
|
|||
{
|
||||
private static PaymentType[] _paymentTypes =
|
||||
{
|
||||
BTCLike, LightningLike,
|
||||
BTCLike, LightningLike, LNURLPay,
|
||||
#if ALTCOINS
|
||||
MoneroLike,
|
||||
EthereumPaymentType.Instance
|
||||
|
@ -31,6 +31,10 @@ namespace BTCPayServer.Payments
|
|||
/// Lightning payment
|
||||
/// </summary>
|
||||
public static LightningPaymentType LightningLike => LightningPaymentType.Instance;
|
||||
/// <summary>
|
||||
/// Lightning payment
|
||||
/// </summary>
|
||||
public static LNURLPayPaymentType LNURLPay => LNURLPayPaymentType.Instance;
|
||||
|
||||
#if ALTCOINS
|
||||
/// <summary>
|
||||
|
@ -84,6 +88,11 @@ namespace BTCPayServer.Payments
|
|||
public abstract object GetGreenfieldData(ISupportedPaymentMethod supportedPaymentMethod, bool canModifyStore);
|
||||
|
||||
public virtual bool IsPaymentType(string paymentType)
|
||||
{
|
||||
return IsPaymentTypeBase(paymentType);
|
||||
}
|
||||
|
||||
protected bool IsPaymentTypeBase(string paymentType)
|
||||
{
|
||||
paymentType = paymentType?.ToLowerInvariant();
|
||||
return new[]
|
||||
|
@ -94,5 +103,8 @@ namespace BTCPayServer.Payments
|
|||
paymentType,
|
||||
StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public abstract void PopulateCryptoInfo(PaymentMethod details, Services.Invoices.InvoiceCryptoInfo invoiceCryptoInfo,
|
||||
string serverUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,10 @@ namespace BTCPayServer.Services.Altcoins.Ethereum.Payments
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -69,6 +69,11 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void PopulateCryptoInfo(PaymentMethod details, InvoiceCryptoInfo invoiceCryptoInfo, string serverUrl)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -19,7 +19,15 @@ using Newtonsoft.Json.Serialization;
|
|||
|
||||
namespace BTCPayServer.Services.Invoices
|
||||
{
|
||||
|
||||
public class InvoiceCryptoInfo : NBitpayClient.InvoiceCryptoInfo
|
||||
{
|
||||
[JsonProperty("paymentUrls")]
|
||||
public new InvoicePaymentUrls PaymentUrls { get; set; }
|
||||
public class InvoicePaymentUrls : NBitpayClient.InvoicePaymentUrls
|
||||
{
|
||||
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
}
|
||||
public class InvoiceMetadata
|
||||
{
|
||||
public static readonly JsonSerializer MetadataSerializer;
|
||||
|
@ -443,19 +451,19 @@ namespace BTCPayServer.Services.Invoices
|
|||
Flags = new Flags() { Refundable = Refundable },
|
||||
PaymentSubtotals = new Dictionary<string, decimal>(),
|
||||
PaymentTotals = new Dictionary<string, decimal>(),
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>(),
|
||||
SupportedTransactionCurrencies = new Dictionary<string, NBitpayClient.InvoiceSupportedTransactionCurrency>(),
|
||||
Addresses = new Dictionary<string, string>(),
|
||||
PaymentCodes = new Dictionary<string, InvoicePaymentUrls>(),
|
||||
PaymentCodes = new Dictionary<string, InvoiceCryptoInfo.InvoicePaymentUrls>(),
|
||||
ExchangeRates = new Dictionary<string, Dictionary<string, decimal>>()
|
||||
};
|
||||
|
||||
dto.Url = ServerUrl.WithTrailingSlash() + $"invoice?id=" + Id;
|
||||
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
|
||||
dto.CryptoInfo = new List<InvoiceCryptoInfo>();
|
||||
dto.MinerFees = new Dictionary<string, MinerFeeInfo>();
|
||||
foreach (var info in this.GetPaymentMethods())
|
||||
{
|
||||
var accounting = info.Calculate();
|
||||
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
|
||||
var cryptoInfo = new InvoiceCryptoInfo();
|
||||
var subtotalPrice = accounting.TotalDue - accounting.NetworkFee;
|
||||
var cryptoCode = info.GetId().CryptoCode;
|
||||
var details = info.GetPaymentMethodDetails();
|
||||
|
@ -500,39 +508,31 @@ namespace BTCPayServer.Services.Invoices
|
|||
}).ToList();
|
||||
|
||||
|
||||
if (details?.Activated is true && paymentId.PaymentType == PaymentTypes.LightningLike)
|
||||
if (details?.Activated is true)
|
||||
{
|
||||
cryptoInfo.PaymentUrls = new InvoicePaymentUrls()
|
||||
|
||||
paymentId.PaymentType.PopulateCryptoInfo(info, cryptoInfo, ServerUrl);
|
||||
if (paymentId.PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
BOLT11 = paymentId.PaymentType.GetPaymentLink(info.Network, details, cryptoInfo.Due,
|
||||
ServerUrl)
|
||||
};
|
||||
}
|
||||
else if (details?.Activated is true && paymentId.PaymentType == PaymentTypes.BTCLike)
|
||||
{
|
||||
var minerInfo = new MinerFeeInfo();
|
||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)details).FeeRate
|
||||
.GetFee(1).Satoshi;
|
||||
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
|
||||
cryptoInfo.PaymentUrls = new InvoicePaymentUrls()
|
||||
{
|
||||
BIP21 = paymentId.PaymentType.GetPaymentLink(info.Network, details, cryptoInfo.Due,
|
||||
ServerUrl)
|
||||
};
|
||||
var minerInfo = new MinerFeeInfo();
|
||||
minerInfo.TotalFee = accounting.NetworkFee.Satoshi;
|
||||
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)details).FeeRate
|
||||
.GetFee(1).Satoshi;
|
||||
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
|
||||
|
||||
#pragma warning disable 618
|
||||
if (info.CryptoCode == "BTC")
|
||||
{
|
||||
dto.BTCPrice = cryptoInfo.Price;
|
||||
dto.Rate = cryptoInfo.Rate;
|
||||
dto.ExRates = cryptoInfo.ExRates;
|
||||
dto.BitcoinAddress = cryptoInfo.Address;
|
||||
dto.BTCPaid = cryptoInfo.Paid;
|
||||
dto.BTCDue = cryptoInfo.Due;
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
if (info.CryptoCode == "BTC")
|
||||
{
|
||||
dto.BTCPrice = cryptoInfo.Price;
|
||||
dto.Rate = cryptoInfo.Rate;
|
||||
dto.ExRates = cryptoInfo.ExRates;
|
||||
dto.BitcoinAddress = cryptoInfo.Address;
|
||||
dto.BTCPaid = cryptoInfo.Paid;
|
||||
dto.BTCDue = cryptoInfo.Due;
|
||||
dto.PaymentUrls = cryptoInfo.PaymentUrls;
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
|
||||
dto.CryptoInfo.Add(cryptoInfo);
|
||||
|
|
|
@ -30,9 +30,9 @@
|
|||
<td>@payment.PaymentMethod</td>
|
||||
@if (Model.ShowAddress)
|
||||
{
|
||||
<td title="@payment.Address">
|
||||
<span class="text-truncate d-block" style="max-width: 400px">@payment.Address</span>
|
||||
</td>
|
||||
<td title="@payment.Address">
|
||||
<span class="text-truncate d-block" style="max-width: 400px">@payment.Address</span>
|
||||
</td>
|
||||
}
|
||||
<td class="text-end">@payment.Rate</td>
|
||||
<td class="text-end">@payment.Paid</td>
|
||||
|
@ -42,6 +42,12 @@
|
|||
<td class="text-end">@payment.Overpaid</td>
|
||||
}
|
||||
</tr>
|
||||
var details = payment.PaymentMethodRaw.GetPaymentMethodDetails();
|
||||
var name = details.GetAdditionalDataPartialName();
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
<partial name="@name" model="@details" />
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
@model BTCPayServer.Payments.LNURLPayPaymentMethodDetails
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ProvidedComment))
|
||||
{
|
||||
|
||||
<tr>
|
||||
<td colspan="100% bg-tile">
|
||||
|
||||
LNURL Comment: @Model.ProvidedComment
|
||||
</td>
|
||||
</tr>
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
<div>
|
||||
<div class="bp-view payment scan" id="scan" v-bind:class="{ 'active': currentTab == 'scan'}">
|
||||
<div class="wrapBtnGroup" v-bind:class="{ invisible: !scanDisplayQr }">
|
||||
<div class="btnGroupLnd">
|
||||
<div class="btnGroupLnd"
|
||||
v-if="srvModel.peerInfo" >
|
||||
<button
|
||||
v-on:click="toggleLightningData('bolt11')"
|
||||
v-bind:class="{ active: currentLightningDisplay === 'bolt11' }"
|
||||
|
@ -48,8 +49,8 @@
|
|||
<img v-bind:src="srvModel.cryptoImage"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatorGem"></div>
|
||||
<div class="copySectionBox">
|
||||
<div class="separatorGem" v-if="srvModel.peerInfo" ></div>
|
||||
<div class="copySectionBox" v-if="srvModel.peerInfo">
|
||||
<label>{{$t("Node Info")}}</label>
|
||||
<div class="inputWithIcon _copyInput">
|
||||
<input type="text" class="checkoutTextbox" v-bind:value="srvModel.peerInfo" readonly="readonly"/>
|
||||
|
|
|
@ -5,5 +5,8 @@
|
|||
<p>
|
||||
<a href="@Model.InvoiceBitcoinUrl" style="word-break: break-word;" rel="noreferrer noopener">@Model.InvoiceBitcoinUrl</a>
|
||||
</p>
|
||||
<p>Peer Info: <b>@Model.PeerInfo</b></p>
|
||||
@if (!string.IsNullOrEmpty(Model.PeerInfo))
|
||||
{
|
||||
<p>Peer Info: <b>@Model.PeerInfo</b></p>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
||||
|
||||
@{
|
||||
var offchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance).Select(payment =>
|
||||
var offchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance || entity.GetPaymentMethodId()?.PaymentType == LNURLPayPaymentType.Instance).Select(payment =>
|
||||
{
|
||||
var offChainPaymentData = payment.GetCryptoPaymentData() as LightningLikePaymentData;
|
||||
if (offChainPaymentData is null)
|
||||
|
@ -13,7 +13,8 @@
|
|||
return new OffChainPaymentViewModel()
|
||||
{
|
||||
Crypto = payment.Network.CryptoCode,
|
||||
BOLT11 = offChainPaymentData.BOLT11
|
||||
BOLT11 = offChainPaymentData.BOLT11,
|
||||
Type = payment.GetCryptoPaymentData().GetPaymentType()
|
||||
};
|
||||
}).Where(model => model != null);
|
||||
}
|
||||
|
@ -28,6 +29,7 @@
|
|||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th class="w-150px">Crypto</th>
|
||||
<th class="w-150px">Type</th>
|
||||
<th>BOLT11</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -36,6 +38,7 @@
|
|||
{
|
||||
<tr>
|
||||
<td>@payment.Crypto</td>
|
||||
<td>@payment.Type.ToPrettyString()</td>
|
||||
<td><div class="wraptextAuto">@payment.BOLT11</div></td>
|
||||
</tr>
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
<h4 class="mb-3">Payment</h4>
|
||||
<h4 class="mb-3">Invoice Settings</h4>
|
||||
@if (Model.PaymentMethods.Any())
|
||||
{
|
||||
<div class="form-group mb-4">
|
||||
|
|
161
BTCPayServer/Views/Stores/Payment.cshtml
Normal file
161
BTCPayServer/Views/Stores/Payment.cshtml
Normal file
|
@ -0,0 +1,161 @@
|
|||
@model PaymentViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData.SetActivePageAndTitle(StoreNavPages.Payment, "Payment", Context.GetStoreData().StoreName);
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-10 col-xl-9">
|
||||
<h4 class="mb-3">Payment</h4>
|
||||
@if (!ViewContext.ModelState.IsValid)
|
||||
{
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
}
|
||||
@if (Model.IsOnchainSetup || Model.IsLightningSetup)
|
||||
{
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="DefaultCurrency" class="form-label"></label>
|
||||
<input asp-for="DefaultCurrency" class="form-control" />
|
||||
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group d-flex align-items-center">
|
||||
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group mt-4">
|
||||
<label asp-for="NetworkFeeMode" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<select asp-for="NetworkFeeMode" class="form-select">
|
||||
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
|
||||
<option value="Always">... on every payment</option>
|
||||
<option value="Never">Never add network fee</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="InvoiceExpiration" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">minutes</span>
|
||||
</div>
|
||||
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="PaymentTolerance" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">percent</span>
|
||||
</div>
|
||||
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DefaultCurrency" class="form-label"></label>
|
||||
<input asp-for="DefaultCurrency" class="form-control" />
|
||||
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
|
||||
</div>
|
||||
@if (Model.IsOnchainSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">On-Chain</h5>
|
||||
@if (Model.CanUsePayJoin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input asp-for="PayJoinEnabled" type="checkbox" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="PayJoinEnabled" class="form-label mb-0 me-1"></label>
|
||||
<a href="https://docs.btcpayserver.org/Payjoin/" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="MonitoringExpiration" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="MonitoringExpiration" class="form-control" style="max-width:10ch;"/>
|
||||
<span class="input-group-text">minutes</span>
|
||||
</div>
|
||||
<span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<select asp-for="SpeedPolicy" class="form-select w-auto" onchange="document.getElementById('unconfirmed-warning').hidden = this.value !== '0';">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="3">Has at least 2 confirmations</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<div class="alert alert-warning my-2" hidden="@(Model.SpeedPolicy != 0)" id="unconfirmed-warning" role="alert">
|
||||
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
|
||||
</div>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="ShowRecommendedFee" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
|
||||
<p class="form-text text-muted mb-0">Fee will be shown for BTC and LTC onchain payments only.</p>
|
||||
</div>
|
||||
<div class="form-group mt-2 mb-4">
|
||||
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
|
||||
<input asp-for="RecommendedFeeBlockTarget" class="form-control" style="width:8ch" min="1" />
|
||||
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.IsLightningSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">Lightning</h5>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningAmountInSatoshi" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="LightningAmountInSatoshi" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningPrivateRouteHints" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="LightningPrivateRouteHints" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="OnChainWithLnInvoiceFallback" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="OnChainWithLnInvoiceFallback" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label asp-for="LightningDescriptionTemplate" class="form-label"></label>
|
||||
<input asp-for="LightningDescriptionTemplate" class="form-control"/>
|
||||
<span asp-validation-for="LightningDescriptionTemplate" class="text-danger"></span>
|
||||
<p class="form-text text-muted">
|
||||
Available placeholders:
|
||||
<code>{StoreName} {ItemDescription} {OrderId}</code>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save" id="Save">Save Payment Settings</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
Please configure either an on-chain wallet or Lightning node first.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<header class="text-center">
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<div class="d-flex mt-4 mb-5">
|
||||
<vc:icon symbol="warning" />
|
||||
<vc:icon symbol="warning"/>
|
||||
<p class="text-secondary text-start mb-0">
|
||||
Please understand that the Lightning Network is still under active development and considered experimental.
|
||||
Before you proceed, take time to familiarize yourself with the risks.
|
||||
|
@ -178,11 +178,50 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-start">
|
||||
<div class="d-flex align-items-center">
|
||||
<input asp-for="LNURLEnabled" type="checkbox" class="btcpay-toggle me-2" data-bs-toggle="collapse" data-bs-target="#LNURLSettings" aria-expanded="@Model.LNURLEnabled" aria-controls="LNURLSettings"/>
|
||||
<label asp-for="LNURLEnabled" class="form-label mb-0 me-1"></label>
|
||||
</div>
|
||||
<div class="collapse @(Model.LNURLEnabled ? "show" : "")" id="LNURLSettings">
|
||||
<h5 class="mb-1" style="padding-top:var(--btcpay-space-l)">LNURL settings</h5>
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center pt-3">
|
||||
<input type="checkbox" asp-for="LNURLBech32Mode" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="LNURLBech32Mode" class="form-label mb-0 me-1"></label>
|
||||
<span asp-validation-for="LNURLBech32Mode" class="text-danger"></span>
|
||||
</div>
|
||||
<p class="form-text text-muted mb-0 ms-5">For wallet compatibility: Bech32 encoded (classic) vs. cleartext URL (upcoming)</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="LNURLStandardInvoiceEnabled" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="LNURLStandardInvoiceEnabled" class="form-label mb-0 me-1"></label>
|
||||
</div>
|
||||
<p class="form-text text-muted mb-0 ms-5">Required for Lightning Address, the pay button and apps.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="DisableBolt11PaymentMethod" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="DisableBolt11PaymentMethod" class="form-label mb-0 me-1"></label>
|
||||
</div>
|
||||
<p class="form-text text-muted mb-0 ms-5">Performance: Turn it off if users should pay only via LNURL.</p>
|
||||
</div>
|
||||
<div class="form-group mb-0 pb-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="checkbox" asp-for="LUD12Enabled" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="LUD12Enabled" class="form-label mb-0 me-1"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-start mt-4">
|
||||
<button id="save" name="command" type="submit" value="save" class="btn btn-primary me-2">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<partial name="_ValidationScriptsPartial"/>
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ namespace BTCPayServer.Views.Stores
|
|||
{
|
||||
public enum StoreNavPages
|
||||
{
|
||||
Index, Create, Rates, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage,
|
||||
PullPayments,
|
||||
Payouts
|
||||
Index, Create, Rates, Payment, Checkout, Tokens, Users, PayButton, Integrations, Wallet, Webhooks, ActivePage, PullPayments, Payouts
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,136 +182,7 @@
|
|||
<input asp-for="StoreWebsite" class="form-control" />
|
||||
<span asp-validation-for="StoreWebsite" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DefaultCurrency" class="form-label"></label>
|
||||
<input asp-for="DefaultCurrency" class="form-control" />
|
||||
<span asp-validation-for="DefaultCurrency" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
@if (Model.IsOnchainSetup || Model.IsLightningSetup)
|
||||
{
|
||||
<h4 class="mt-5 mb-3">Payment</h4>
|
||||
<div class="form-group d-flex align-items-center">
|
||||
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-2" />
|
||||
<label asp-for="AnyoneCanCreateInvoice" class="form-label mb-0 me-1"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group mt-4">
|
||||
<label asp-for="NetworkFeeMode" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<select asp-for="NetworkFeeMode" class="form-select">
|
||||
<option value="MultiplePaymentsOnly">... only if the customer makes more than one payment for the invoice</option>
|
||||
<option value="Always">... on every payment</option>
|
||||
<option value="Never">Never add network fee</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="InvoiceExpiration" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="InvoiceExpiration" class="form-control" style="max-width:10ch;" />
|
||||
<span class="input-group-text">minutes</span>
|
||||
</div>
|
||||
<span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="PaymentTolerance" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="PaymentTolerance" class="form-control" style="max-width:10ch;" />
|
||||
<span class="input-group-text">percent</span>
|
||||
</div>
|
||||
<span asp-validation-for="PaymentTolerance" class="text-danger"></span>
|
||||
</div>
|
||||
@if (Model.IsOnchainSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">On-Chain</h5>
|
||||
@if (Model.CanUsePayJoin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input asp-for="PayJoinEnabled" type="checkbox" class="btcpay-toggle me-2" />
|
||||
<label asp-for="PayJoinEnabled" class="form-label mb-0 me-1"></label>
|
||||
<a href="https://docs.btcpayserver.org/Payjoin/" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<span asp-validation-for="PayJoinEnabled" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="MonitoringExpiration" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<div class="input-group">
|
||||
<input asp-for="MonitoringExpiration" class="form-control" style="max-width:10ch;" />
|
||||
<span class="input-group-text">minutes</span>
|
||||
</div>
|
||||
<span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SpeedPolicy" class="form-label"></label>
|
||||
<a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<select asp-for="SpeedPolicy" class="form-select w-auto">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="3">Has at least 2 confirmations</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<div class="alert alert-warning my-2" hidden="@(Model.SpeedPolicy != 0)" id="unconfirmed-warning" role="alert">
|
||||
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
|
||||
</div>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="ShowRecommendedFee" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="ShowRecommendedFee" class="form-check-label"></label>
|
||||
<p class="form-text text-muted mb-0">Fee will be shown for BTC and LTC onchain payments only.</p>
|
||||
</div>
|
||||
<div class="form-group mt-2 mb-4">
|
||||
<label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
|
||||
<input asp-for="RecommendedFeeBlockTarget" class="form-control" style="width:8ch" min="1" />
|
||||
<span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.IsLightningSetup)
|
||||
{
|
||||
<h5 class="mt-5 mb-3">Lightning</h5>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningAmountInSatoshi" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="LightningAmountInSatoshi" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="LightningPrivateRouteHints" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="LightningPrivateRouteHints" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-check my-1">
|
||||
<input asp-for="OnChainWithLnInvoiceFallback" type="checkbox" class="form-check-input" />
|
||||
<label asp-for="OnChainWithLnInvoiceFallback" class="form-check-label"></label>
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label asp-for="LightningDescriptionTemplate" class="form-label"></label>
|
||||
<input asp-for="LightningDescriptionTemplate" class="form-control" />
|
||||
<span asp-validation-for="LightningDescriptionTemplate" class="text-danger"></span>
|
||||
<p class="form-text text-muted">
|
||||
Available placeholders:
|
||||
<code>{StoreName} {ItemDescription} {OrderId}</code>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<button name="command" type="submit" class="btn btn-primary" value="Save" id="Save">Save Store Settings</button>
|
||||
</form>
|
||||
|
||||
|
@ -356,9 +227,4 @@
|
|||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<script>
|
||||
delegate('change', '#SpeedPolicy', e => {
|
||||
document.getElementById('unconfirmed-warning').hidden = e.target.value !== '0';
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<nav id="sideNav" class="nav flex-column mb-4">
|
||||
<a id="@(nameof(StoreNavPages.Index))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Index)" asp-controller="Stores" asp-action="UpdateStore" asp-route-storeId="@this.Context.GetRouteValue("storeId")">General settings</a>
|
||||
<a id="@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="Stores" asp-action="Rates" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Rates</a>
|
||||
<a id="@(nameof(StoreNavPages.Checkout))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Checkout)" asp-controller="Stores" asp-action="CheckoutExperience" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Checkout experience</a>
|
||||
<a id="@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="Stores" asp-action="ListTokens" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Access Tokens</a>
|
||||
<a id="@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Users</a>
|
||||
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pay Button</a>
|
||||
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Integrations</a>
|
||||
<a id="@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Webhooks</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="PullPayments">Pull payments</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@this.Context.GetRouteValue("storeId")" id="Payouts">Payouts</a>
|
||||
<a id="@(nameof(StoreNavPages.Index))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Index)" asp-controller="Stores" asp-action="UpdateStore" asp-route-storeId="@Context.GetRouteValue("storeId")">General settings</a>
|
||||
<a id="@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="Stores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a>
|
||||
<a id="@(nameof(StoreNavPages.Payment))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payment)" asp-controller="Stores" asp-action="Payment" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment</a>
|
||||
<a id="@(nameof(StoreNavPages.Checkout))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Checkout)" asp-controller="Stores" asp-action="CheckoutExperience" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout experience</a>
|
||||
<a id="@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="Stores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a>
|
||||
<a id="@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@Context.GetRouteValue("storeId")">Users</a>
|
||||
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Context.GetRouteValue("storeId")">Pay Button</a>
|
||||
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@Context.GetRouteValue("storeId")">Integrations</a>
|
||||
<a id="@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@Context.GetRouteValue("storeId")">Webhooks</a>
|
||||
<a id="@(nameof(StoreNavPages.PullPayments))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Pull payments</a>
|
||||
<a id="@(nameof(StoreNavPages.Payouts))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Payouts</a>
|
||||
<vc:ui-extension-point location="store-nav" model="@Model" />
|
||||
</nav>
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
{
|
||||
"paths": {
|
||||
"/api/v1/stores/{storeId}/payment-methods/LNURL": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Payment Methods (LNURL)"
|
||||
],
|
||||
"summary": "Get store LNURL payment methods",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Fetch payment methods that are enabled/disabled only",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "View information about the stores' configured LNURL payment methods",
|
||||
"operationId": "StoreLNURLPayPaymentMethods_GetLNURLPayPaymentMethods",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "list of payment methods",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodDataList"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/payment-methods/LNURL/{cryptoCode}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Store Payment Methods (LNURL Pay)"
|
||||
],
|
||||
"summary": "Get store LNURL Pay payment method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The crypto code of the payment method to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "View information about the specified payment method",
|
||||
"operationId": "StoreLNURLPayPaymentMethods_GetLNURLPayPaymentMethod",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "specified payment method",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodData"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to view the specified store"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store/payment method"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"Store Payment Methods (LNURL Pay)"
|
||||
],
|
||||
"summary": "Update store LNURL Pay payment method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The crypto code of the payment method to update",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Update the specified store's payment method",
|
||||
"requestBody": {
|
||||
"x-name": "request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodData"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true,
|
||||
"x-position": 1
|
||||
},
|
||||
"operationId": "StoreLNURLPayPaymentMethods_UpdateLNURLPayPaymentMethod",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "updated specified payment method",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodData"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when updating the store payment method",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to update the specified store"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Store Payment Methods (LNURL Pay)"
|
||||
],
|
||||
"summary": "Remove store LNURL Pay payment method",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "storeId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The store to fetch",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cryptoCode",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The crypto code of the payment method to update",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Removes the specified store payment method.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The payment method has been removed"
|
||||
},
|
||||
"400": {
|
||||
"description": "A list of errors that occurred when removing the payment method",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden to remove the specified payment method"
|
||||
},
|
||||
"404": {
|
||||
"description": "The key is not found for this store/payment-method"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"API Key": [
|
||||
"btcpay.store.canmodifystoresettings"
|
||||
],
|
||||
"Basic": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"LNURLPayPaymentMethodDataList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodData"
|
||||
}
|
||||
},
|
||||
"LNURLPayPaymentMethodBaseData": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"useBech32Scheme": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use [LUD-01](https://github.com/fiatjaf/lnurl-rfc/blob/luds/01.md)'s bech32 format or to use [LUD-17](https://github.com/fiatjaf/lnurl-rfc/blob/luds/17.md) url formatting. "
|
||||
},
|
||||
"enableForStandardInvoices": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to allow this payment method to also be used for standard invoices and not just topup invoices."
|
||||
},
|
||||
"lud12Enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Allow comments to be passed on via lnurl."
|
||||
}
|
||||
}
|
||||
},
|
||||
"LNURLPayPaymentMethodData": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/LNURLPayPaymentMethodBaseData"
|
||||
},
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the payment method is enabled. Note that this can only enabled when a Lightning Network payment method is available and enabled"
|
||||
},
|
||||
"cryptoCode": {
|
||||
"type": "string",
|
||||
"description": "Crypto code of the payment method"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Store Payment Methods (LNURL Pay)"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue