mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 17:26:05 +01:00
Fix: Using lnaddresses on Nostr should not result in lots of invoice being created
This commit is contained in:
parent
db83d238d5
commit
4afec2e2b6
2 changed files with 83 additions and 14 deletions
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -2379,7 +2380,7 @@ namespace BTCPayServer.Tests
|
||||||
|
|
||||||
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
|
var addresses = s.Driver.FindElements(By.ClassName("lightning-address-value"));
|
||||||
Assert.Equal(2, addresses.Count);
|
Assert.Equal(2, addresses.Count);
|
||||||
|
var callbacks = new List<Uri>();
|
||||||
foreach (IWebElement webElement in addresses)
|
foreach (IWebElement webElement in addresses)
|
||||||
{
|
{
|
||||||
var value = webElement.GetAttribute("value");
|
var value = webElement.GetAttribute("value");
|
||||||
|
@ -2397,6 +2398,7 @@ namespace BTCPayServer.Tests
|
||||||
lnaddress2 = m["text/identifier"];
|
lnaddress2 = m["text/identifier"];
|
||||||
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
Assert.Equal(2, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
Assert.Equal(10, request.MaxSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
|
callbacks.Add(request.Callback);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case { } v when v.StartsWith(lnaddress1):
|
case { } v when v.StartsWith(lnaddress1):
|
||||||
|
@ -2404,6 +2406,7 @@ namespace BTCPayServer.Tests
|
||||||
lnaddress1 = m["text/identifier"];
|
lnaddress1 = m["text/identifier"];
|
||||||
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
Assert.Equal(1, request.MinSendable.ToDecimal(LightMoneyUnit.Satoshi));
|
||||||
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
Assert.Equal(6.12m, request.MaxSendable.ToDecimal(LightMoneyUnit.BTC));
|
||||||
|
callbacks.Add(request.Callback);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Assert.False(true, "Should have matched");
|
Assert.False(true, "Should have matched");
|
||||||
|
@ -2411,7 +2414,19 @@ namespace BTCPayServer.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var repo = s.Server.PayTester.GetService<InvoiceRepository>();
|
var repo = s.Server.PayTester.GetService<InvoiceRepository>();
|
||||||
|
|
||||||
var invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
|
var invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
|
||||||
|
// Resolving a ln address shouldn't create any btcpay invoice.
|
||||||
|
// This must be done because some NOST clients resolve ln addresses preemptively without user interaction
|
||||||
|
Assert.Empty(invoices);
|
||||||
|
|
||||||
|
// Calling the callbacks should create the invoices
|
||||||
|
foreach (var callback in callbacks)
|
||||||
|
{
|
||||||
|
using var r = await s.Server.PayTester.HttpClient.GetAsync(callback);
|
||||||
|
await r.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
invoices = await repo.GetInvoices(new InvoiceQuery() { StoreId = new[] { s.StoreId } });
|
||||||
Assert.Equal(2, invoices.Length);
|
Assert.Equal(2, invoices.Length);
|
||||||
var emailSuffix = $"@{s.Server.PayTester.HostName}:{s.Server.PayTester.Port}";
|
var emailSuffix = $"@{s.Server.PayTester.HostName}:{s.Server.PayTester.Port}";
|
||||||
foreach (var i in invoices)
|
foreach (var i in invoices)
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
@ -373,13 +374,52 @@ namespace BTCPayServer
|
||||||
return NotFound("Unknown username");
|
return NotFound("Unknown username");
|
||||||
|
|
||||||
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
|
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
|
||||||
|
var cryptoCode = "BTC";
|
||||||
if (store is null)
|
if (store is null)
|
||||||
return NotFound("Unknown username");
|
return NotFound("Unknown username");
|
||||||
|
if (GetLNUrlPaymentMethodId(cryptoCode, store, out var lnUrlMethod) is null)
|
||||||
|
return NotFound("LNUrl not available for store");
|
||||||
|
|
||||||
var blob = lightningAddressSettings.GetBlob();
|
var blob = lightningAddressSettings.GetBlob();
|
||||||
|
|
||||||
return await GetLNURLRequest(
|
var lnurlRequest = new LNURLPayRequest()
|
||||||
"BTC",
|
{
|
||||||
|
Tag = "payRequest",
|
||||||
|
MinSendable = blob?.Min is decimal min ? new LightMoney(min, LightMoneyUnit.Satoshi) : null,
|
||||||
|
MaxSendable = blob?.Max is decimal max ? new LightMoney(max, LightMoneyUnit.Satoshi) : null,
|
||||||
|
CommentAllowed = lnUrlMethod.LUD12Enabled ? 2000 : 0
|
||||||
|
};
|
||||||
|
NormalizeSendable(lnurlRequest);
|
||||||
|
|
||||||
|
var lnUrlMetadata = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
["text/identifier"] = $"{username}@{Request.Host}"
|
||||||
|
};
|
||||||
|
SetLNUrlDescriptionMetadata(lnUrlMetadata, store, store.GetStoreBlob(), null);
|
||||||
|
lnurlRequest.Metadata =
|
||||||
|
JsonConvert.SerializeObject(lnUrlMetadata.Select(kv => new[] { kv.Key, kv.Value }));
|
||||||
|
|
||||||
|
lnurlRequest.Callback = new Uri(_linkGenerator.GetUriByAction(
|
||||||
|
action: nameof(GetLNURLForLightningAddress),
|
||||||
|
controller: "UILNURL",
|
||||||
|
values: new { cryptoCode, username }, Request.Scheme, Request.Host, Request.PathBase));
|
||||||
|
|
||||||
|
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
|
||||||
|
return Ok(lnurlRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("pay/lnaddress/{username}")]
|
||||||
|
[EnableCors(CorsPolicies.All)]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
public async Task<IActionResult> GetLNURLForLightningAddress(string cryptoCode, string username, [FromQuery] long? amount = null, string comment = null)
|
||||||
|
{
|
||||||
|
var lightningAddressSettings = await _lightningAddressService.ResolveByAddress(username);
|
||||||
|
if (lightningAddressSettings is null || username is null)
|
||||||
|
return NotFound("Unknown username");
|
||||||
|
var blob = lightningAddressSettings.GetBlob();
|
||||||
|
var store = await _storeRepository.FindStore(lightningAddressSettings.StoreDataId);
|
||||||
|
var result = await GetLNURLRequest(
|
||||||
|
cryptoCode,
|
||||||
store,
|
store,
|
||||||
store.GetStoreBlob(),
|
store.GetStoreBlob(),
|
||||||
new CreateInvoiceRequest()
|
new CreateInvoiceRequest()
|
||||||
|
@ -396,6 +436,10 @@ namespace BTCPayServer
|
||||||
{
|
{
|
||||||
{ "text/identifier", $"{username}@{Request.Host}" }
|
{ "text/identifier", $"{username}@{Request.Host}" }
|
||||||
});
|
});
|
||||||
|
if (result is not OkObjectResult ok || ok.Value is not LNURLPayRequest payRequest)
|
||||||
|
return result;
|
||||||
|
var invoiceId = payRequest.Callback.AbsoluteUri.Split('/').Last();
|
||||||
|
return await GetLNURLForInvoice(invoiceId, cryptoCode, amount, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -482,11 +526,7 @@ namespace BTCPayServer
|
||||||
|
|
||||||
if (!lnUrlMetadata.ContainsKey("text/plain"))
|
if (!lnUrlMetadata.ContainsKey("text/plain"))
|
||||||
{
|
{
|
||||||
var invoiceDescription = blob.LightningDescriptionTemplate
|
SetLNUrlDescriptionMetadata(lnUrlMetadata, store, blob, i.Metadata);
|
||||||
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
|
||||||
.Replace("{ItemDescription}", i.Metadata.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
|
||||||
.Replace("{OrderId}", i.Metadata.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
|
||||||
lnUrlMetadata.Add("text/plain", invoiceDescription);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lnurlRequest.Tag = "payRequest";
|
lnurlRequest.Tag = "payRequest";
|
||||||
|
@ -503,12 +543,7 @@ namespace BTCPayServer
|
||||||
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
lnurlRequest.MaxSendable = lnurlRequest.MinSendable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't think BTCPay handle well 0 sats payments, just in case make it minimum one sat.
|
NormalizeSendable(lnurlRequest);
|
||||||
if (lnurlRequest.MinSendable is null || lnurlRequest.MinSendable < LightMoney.Satoshis(1.0m))
|
|
||||||
lnurlRequest.MinSendable = LightMoney.Satoshis(1.0m);
|
|
||||||
|
|
||||||
if (lnurlRequest.MaxSendable is null)
|
|
||||||
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
|
|
||||||
|
|
||||||
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
|
lnurlRequest = await _pluginHookService.ApplyFilter("modify-lnurlp-request", lnurlRequest) as LNURLPayRequest;
|
||||||
if (paymentMethodDetails.PayRequest is null)
|
if (paymentMethodDetails.PayRequest is null)
|
||||||
|
@ -524,6 +559,25 @@ namespace BTCPayServer
|
||||||
return lnurlRequest;
|
return lnurlRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetLNUrlDescriptionMetadata(Dictionary<string, string> lnUrlMetadata, Data.StoreData store, StoreBlob blob, InvoiceMetadata invoiceMetadata)
|
||||||
|
{
|
||||||
|
var invoiceDescription = blob.LightningDescriptionTemplate
|
||||||
|
.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("{ItemDescription}", invoiceMetadata?.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("{OrderId}", invoiceMetadata?.OrderId ?? "", StringComparison.OrdinalIgnoreCase);
|
||||||
|
lnUrlMetadata.Add("text/plain", invoiceDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void NormalizeSendable(LNURLPayRequest lnurlRequest)
|
||||||
|
{
|
||||||
|
// We don't think BTCPay handle well 0 sats payments, just in case make it minimum one sat.
|
||||||
|
if (lnurlRequest.MinSendable is null || lnurlRequest.MinSendable < LightMoney.Satoshis(1.0m))
|
||||||
|
lnurlRequest.MinSendable = LightMoney.Satoshis(1.0m);
|
||||||
|
|
||||||
|
if (lnurlRequest.MaxSendable is null)
|
||||||
|
lnurlRequest.MaxSendable = LightMoney.FromUnit(6.12m, LightMoneyUnit.BTC);
|
||||||
|
}
|
||||||
|
|
||||||
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaySupportedPaymentMethod lnUrlSettings)
|
PaymentMethodId GetLNUrlPaymentMethodId(string cryptoCode, Data.StoreData store, out LNURLPaySupportedPaymentMethod lnUrlSettings)
|
||||||
{
|
{
|
||||||
lnUrlSettings = null;
|
lnUrlSettings = null;
|
||||||
|
|
Loading…
Add table
Reference in a new issue