Simplify extension of payments extensions

This commit is contained in:
nicolas.dorier
2024-10-07 18:19:19 +09:00
parent e1bfc04451
commit 34b2cca492
15 changed files with 55 additions and 126 deletions

View File

@@ -3264,6 +3264,7 @@ namespace BTCPayServer.Tests
public async Task CanUseLNAddress() public async Task CanUseLNAddress()
{ {
using var s = CreateSeleniumTester(); using var s = CreateSeleniumTester();
s.Server.DeleteStore = false;
s.Server.ActivateLightning(); s.Server.ActivateLightning();
await s.StartAsync(); await s.StartAsync();
await s.Server.EnsureChannelsSetup(); await s.Server.EnsureChannelsSetup();
@@ -3416,7 +3417,13 @@ namespace BTCPayServer.Tests
var succ = JsonConvert.DeserializeObject<LNURLPayRequest.LNURLPayRequestCallbackResponse>(str); var succ = JsonConvert.DeserializeObject<LNURLPayRequest.LNURLPayRequestCallbackResponse>(str);
Assert.NotNull(succ.Pr); Assert.NotNull(succ.Pr);
Assert.Equal(new LightMoney(2001), BOLT11PaymentRequest.Parse(succ.Pr, Network.RegTest).MinimumAmount); Assert.Equal(new LightMoney(2001), BOLT11PaymentRequest.Parse(succ.Pr, Network.RegTest).MinimumAmount);
await s.Server.CustomerLightningD.Pay(succ.Pr);
} }
// Can we find our comment and address in the payment list?
s.GoToInvoices();
var source = s.Driver.PageSource;
Assert.Contains(lnUsername, source);
} }
[Fact] [Fact]

View File

@@ -609,9 +609,7 @@ retry:
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId); var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
var method = methods.First(m => m.PaymentMethodId == $"{cryptoCode}-LN"); var method = methods.First(m => m.PaymentMethodId == $"{cryptoCode}-LN");
var bolt11 = method.Destination; var bolt11 = method.Destination;
TestLogs.LogInformation("PAYING");
await parent.CustomerLightningD.Pay(bolt11); await parent.CustomerLightningD.Pay(bolt11);
TestLogs.LogInformation("PAID");
await WaitInvoicePaid(invoiceId); await WaitInvoicePaid(invoiceId);
} }

View File

@@ -68,7 +68,7 @@ public class StoreRecentInvoices : ViewComponent
Details = new InvoiceDetailsModel Details = new InvoiceDetailsModel
{ {
Archived = invoice.Archived, Archived = invoice.Archived,
Payments = invoice.GetPayments(false) Payments = invoice.GetPayments(false)
} }
}).ToList(); }).ToList();

View File

@@ -128,6 +128,7 @@ namespace BTCPayServer.Controllers
StoreLink = Url.Action(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId = store.Id }), StoreLink = Url.Action(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId = store.Id }),
PaymentRequestLink = Url.Action(nameof(UIPaymentRequestController.ViewPaymentRequest), "UIPaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }), PaymentRequestLink = Url.Action(nameof(UIPaymentRequestController.ViewPaymentRequest), "UIPaymentRequest", new { payReqId = invoice.Metadata.PaymentRequestId }),
Id = invoice.Id, Id = invoice.Id,
Entity = invoice,
State = invoiceState, State = invoiceState,
TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" : TransactionSpeed = invoice.SpeedPolicy == SpeedPolicy.HighSpeed ? "high" :
invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" : invoice.SpeedPolicy == SpeedPolicy.MediumSpeed ? "medium" :
@@ -554,6 +555,7 @@ namespace BTCPayServer.Controllers
{ {
Archived = invoice.Archived, Archived = invoice.Archived,
Payments = invoice.GetPayments(false), Payments = invoice.GetPayments(false),
Entity = invoice,
CryptoPayments = invoice.GetPaymentPrompts().Select( CryptoPayments = invoice.GetPaymentPrompts().Select(
data => data =>
{ {

View File

@@ -63,7 +63,6 @@ namespace BTCPayServer.Controllers
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly TransactionLinkProviders _transactionLinkProviders; private readonly TransactionLinkProviders _transactionLinkProviders;
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions; private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
private readonly PaymentMethodViewProvider _viewProvider;
private readonly PrettyNameProvider _prettyName; private readonly PrettyNameProvider _prettyName;
private readonly AppService _appService; private readonly AppService _appService;
private readonly IFileService _fileService; private readonly IFileService _fileService;
@@ -99,7 +98,6 @@ namespace BTCPayServer.Controllers
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
TransactionLinkProviders transactionLinkProviders, TransactionLinkProviders transactionLinkProviders,
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions, Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
PaymentMethodViewProvider viewProvider,
PrettyNameProvider prettyName) PrettyNameProvider prettyName)
{ {
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
@@ -124,7 +122,6 @@ namespace BTCPayServer.Controllers
_authorizationService = authorizationService; _authorizationService = authorizationService;
_transactionLinkProviders = transactionLinkProviders; _transactionLinkProviders = transactionLinkProviders;
_paymentModelExtensions = paymentModelExtensions; _paymentModelExtensions = paymentModelExtensions;
_viewProvider = viewProvider;
_prettyName = prettyName; _prettyName = prettyName;
_fileService = fileService; _fileService = fileService;
_uriResolver = uriResolver; _uriResolver = uriResolver;

View File

@@ -292,10 +292,9 @@ namespace BTCPayServer
} }
public static IServiceCollection AddUIExtension(this IServiceCollection services, string key, string partialView) public static IServiceCollection AddUIExtension(this IServiceCollection services, string location, string partialViewName)
{ {
services.AddSingleton<IUIExtension>(new UIExtension(partialView, services.AddSingleton<IUIExtension>(new UIExtension(partialViewName, location));
key));
return services; return services;
} }
public static IServiceCollection AddReportProvider<T>(this IServiceCollection services) public static IServiceCollection AddReportProvider<T>(this IServiceCollection services)

View File

@@ -158,8 +158,7 @@ namespace BTCPayServer.Hosting
// //
AddOnchainWalletParsers(services); AddOnchainWalletParsers(services);
services.AddSingleton<IUIExtension>(new UIExtension("Bitcoin/ViewBitcoinLikePaymentData", "store-invoices-payments"));
services.AddSingleton<IUIExtension>(new UIExtension("Lightning/ViewLightningLikePaymentData", "store-invoices-payments"));
services.AddStartupTask<BlockExplorerLinkStartupTask>(); services.AddStartupTask<BlockExplorerLinkStartupTask>();
services.AddStartupTask<LoadCurrencyNameTableStartupTask>(); services.AddStartupTask<LoadCurrencyNameTableStartupTask>();
@@ -367,6 +366,8 @@ namespace BTCPayServer.Hosting
services.AddUIExtension("checkout-end", "Bitcoin/BitcoinLikeMethodCheckout"); services.AddUIExtension("checkout-end", "Bitcoin/BitcoinLikeMethodCheckout");
services.AddUIExtension("checkout-end", "Lightning/LightningLikeMethodCheckout"); services.AddUIExtension("checkout-end", "Lightning/LightningLikeMethodCheckout");
services.AddUIExtension("store-invoices-payments", "Bitcoin/ViewBitcoinLikePaymentData");
services.AddUIExtension("store-invoices-payments", "Lightning/ViewLightningLikePaymentData");
services.AddSingleton<Services.NBXplorerConnectionFactory>(); services.AddSingleton<Services.NBXplorerConnectionFactory>();
services.AddSingleton<IHostedService, Services.NBXplorerConnectionFactory>(o => o.GetRequiredService<Services.NBXplorerConnectionFactory>()); services.AddSingleton<IHostedService, Services.NBXplorerConnectionFactory>(o => o.GetRequiredService<Services.NBXplorerConnectionFactory>());
@@ -407,7 +408,6 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
services.AddSingleton<IHostedService, LightningPendingPayoutListener>(); services.AddSingleton<IHostedService, LightningPendingPayoutListener>();
services.AddSingleton<PaymentMethodHandlerDictionary>(); services.AddSingleton<PaymentMethodHandlerDictionary>();
services.AddSingleton<PaymentMethodViewProvider>();
services.AddSingleton<PayoutMethodHandlerDictionary>(); services.AddSingleton<PayoutMethodHandlerDictionary>();
@@ -668,8 +668,6 @@ o.GetRequiredService<IEnumerable<IPaymentLinkExtension>>().ToDictionary(o => o.P
(IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentLinkExtension), new object[] { network, pmi })); (IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentLinkExtension), new object[] { network, pmi }));
services.AddSingleton<IPaymentModelExtension>(provider => services.AddSingleton<IPaymentModelExtension>(provider =>
(IPaymentModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentModelExtension), new object[] { network, pmi })); (IPaymentModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentModelExtension), new object[] { network, pmi }));
services.AddSingleton<IPaymentMethodViewExtension>(provider =>
(IPaymentMethodViewExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPaymentMethodViewExtension), new object[] { pmi }));
services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider => services.AddSingleton<IPaymentMethodBitpayAPIExtension>(provider =>
(IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentMethodBitpayAPIExtension), new object[] { pmi })); (IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentMethodBitpayAPIExtension), new object[] { pmi }));
} }

View File

@@ -136,5 +136,6 @@ namespace BTCPayServer.Models.InvoicingModels
public bool HasRefund { get; set; } public bool HasRefund { get; set; }
public bool StillDue { get; set; } public bool StillDue { get; set; }
public bool HasRates { get; set; } public bool HasRates { get; set; }
public InvoiceEntity Entity { get; internal set; }
} }
} }

View File

@@ -1,59 +0,0 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Payments
{
public interface IPaymentMethodViewExtension
{
PaymentMethodId PaymentMethodId { get; }
void RegisterViews(PaymentMethodViewContext context);
}
public record ViewViewModel(object View, object ViewModel);
public class PaymentMethodViewProvider
{
private readonly Dictionary<PaymentMethodId, IPaymentMethodViewExtension> _extensions;
private readonly PaymentMethodHandlerDictionary _handlers;
public PaymentMethodViewProvider(
IEnumerable<IPaymentMethodViewExtension> extensions,
PaymentMethodHandlerDictionary handlers)
{
_extensions = extensions.ToDictionary(o => o.PaymentMethodId, o => o);
_handlers = handlers;
}
public ViewViewModel? TryGetViewViewModel(PaymentPrompt paymentPrompt, string key)
{
if (!_extensions.TryGetValue(paymentPrompt.PaymentMethodId, out var extension))
return null;
if (!_handlers.TryGetValue(paymentPrompt.PaymentMethodId, out var handler) || paymentPrompt.Details is null)
return null;
var ctx = new PaymentMethodViewContext()
{
Details = handler.ParsePaymentPromptDetails(paymentPrompt.Details)
};
extension.RegisterViews(ctx);
object? view = null;
if (!ctx._Views.TryGetValue(key, out view))
return null;
return new ViewViewModel(view, handler.ParsePaymentPromptDetails(paymentPrompt.Details));
}
}
public class PaymentMethodViewContext
{
internal Dictionary<string, object> _Views = new Dictionary<string, object>();
public object? Details { get; internal set; }
public void RegisterPaymentMethodDetails(string partialName)
{
_Views.Add("AdditionalPaymentMethodDetails", partialName);
}
public void Register(string key, object value)
{
_Views.Add(key, value);
}
}
}

View File

@@ -1,19 +0,0 @@
namespace BTCPayServer.Payments.LNURLPay
{
public class LNURLPaymentMethodViewExtension : IPaymentMethodViewExtension
{
public LNURLPaymentMethodViewExtension(PaymentMethodId paymentMethodId)
{
PaymentMethodId = paymentMethodId;
}
public PaymentMethodId PaymentMethodId { get; }
public void RegisterViews(PaymentMethodViewContext context)
{
var details = context.Details;
if (details is not LNURLPayPaymentMethodDetails d)
return;
context.RegisterPaymentMethodDetails("LNURL/AdditionalPaymentMethodDetails");
}
}
}

View File

@@ -21,9 +21,9 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments.Lightning namespace BTCPayServer.Payments.Lightning
{ {
public interface ILightningPaymentHandler : IHasNetwork public interface ILightningPaymentHandler : IHasNetwork, IPaymentMethodHandler
{ {
LightningPaymentData ParsePaymentDetails(JToken details); new LightningPaymentData ParsePaymentDetails(JToken details);
} }
public class LightningLikePaymentHandler : IPaymentMethodHandler, ILightningPaymentHandler public class LightningLikePaymentHandler : IPaymentMethodHandler, ILightningPaymentHandler
{ {

View File

@@ -7,10 +7,10 @@
@inject DisplayFormatter DisplayFormatter @inject DisplayFormatter DisplayFormatter
@inject TransactionLinkProviders TransactionLinkProviders @inject TransactionLinkProviders TransactionLinkProviders
@inject PaymentMethodHandlerDictionary handlers @inject PaymentMethodHandlerDictionary handlers
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity> @model InvoiceDetailsModel
@{ @{
PayjoinInformation payjoinInformation = null; PayjoinInformation payjoinInformation = null;
var payments = Model var payments = Model.Payments
.Select(payment => .Select(payment =>
{ {
if (!handlers.TryGetValue(payment.PaymentMethodId, out var h) || h is not BitcoinLikePaymentHandler handler) if (!handlers.TryGetValue(payment.PaymentMethodId, out var h) || h is not BitcoinLikePaymentHandler handler)

View File

@@ -1,18 +0,0 @@
@model BTCPayServer.Payments.LNURLPayPaymentMethodDetails
@if (!string.IsNullOrEmpty(Model.ProvidedComment))
{
<tr>
<td colspan="100% bg-tile">
LNURL Comment: @Model.ProvidedComment
</td>
</tr>
}
@if (!string.IsNullOrEmpty(Model.ConsumedLightningAddress))
{
<tr>
<td colspan="100% bg-tile">
Lightning address used: @Model.ConsumedLightningAddress
</td>
</tr>
}

View File

@@ -7,16 +7,31 @@
@inject DisplayFormatter DisplayFormatter @inject DisplayFormatter DisplayFormatter
@inject PaymentMethodHandlerDictionary handlers @inject PaymentMethodHandlerDictionary handlers
@inject PrettyNameProvider prettyName @inject PrettyNameProvider prettyName
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity> @model InvoiceDetailsModel
@{ @{
string providedComment = null;
string consumedLightningAddress = null;
var payments = Model var payments = Model
.Payments
.Select(payment => .Select(payment =>
{ {
if (handlers.TryGet(payment.PaymentMethodId) is not ILightningPaymentHandler handler) if (handlers.TryGet(payment.PaymentMethodId) is not ILightningPaymentHandler handler)
return null; return null;
var offChainPaymentData = handler.ParsePaymentDetails(payment.Details); var offChainPaymentData = handler.ParsePaymentDetails(payment.Details);
if (handler.ParsePaymentPromptDetails(Model.Entity.GetPaymentPrompt(payment.PaymentMethodId)?.Details) is LNURLPayPaymentMethodDetails lnurlPrompt)
{
if (lnurlPrompt.ConsumedLightningAddress is string consumed)
{
consumedLightningAddress = consumed;
}
if (lnurlPrompt.ProvidedComment is string comment)
{
providedComment = comment;
}
}
return new OffChainPaymentViewModel return new OffChainPaymentViewModel
{ {
Type = prettyName.PrettyName(payment.PaymentMethodId), Type = prettyName.PrettyName(payment.PaymentMethodId),
@@ -25,8 +40,8 @@
Amount = DisplayFormatter.Currency(payment.Value, handler.Network.CryptoCode, divisibility: payment.Divisibility) Amount = DisplayFormatter.Currency(payment.Value, handler.Network.CryptoCode, divisibility: payment.Divisibility)
}; };
}) })
.Where(model => model != null) .Where(model => model != null)
.ToList(); .ToList();
} }
@if (payments.Any()) @if (payments.Any())
@@ -37,7 +52,7 @@
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead> <thead>
<tr> <tr>
<th class="w-75px">Type</th> <th class="w-175px">Type</th>
<th class="w-175px">Destination</th> <th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th> <th class="text-nowrap">Payment Proof</th>
<th class="w-150px text-end">Paid</th> <th class="w-150px text-end">Paid</th>
@@ -61,6 +76,19 @@
} }
</tbody> </tbody>
</table> </table>
@if (!string.IsNullOrEmpty(providedComment))
{
<div>
<b>LNURL Comment</b>: @providedComment
</div>
}
@if (!string.IsNullOrEmpty(consumedLightningAddress))
{
<div>
<b>Lightning address used</b>: @consumedLightningAddress
</div>
}
</div> </div>
</section> </section>
} }

View File

@@ -1,5 +1,4 @@
@using BTCPayServer.Payments @using BTCPayServer.Payments
@inject PaymentMethodViewProvider paymentMethodViewProvider
@model (InvoiceDetailsModel Invoice, bool ShowAddress) @model (InvoiceDetailsModel Invoice, bool ShowAddress)
@{ @{
var invoice = Model.Invoice; var invoice = Model.Invoice;
@@ -87,14 +86,10 @@
} }
</td> </td>
</tr> </tr>
var vvm = paymentMethodViewProvider.TryGetViewViewModel(payment.PaymentMethodRaw, "AdditionalPaymentMethodDetails");; <vc:ui-extension-point location="invoice-payments-list" />
if (vvm != null)
{
<partial name="@((string)vvm.View)" model="@vvm.ViewModel" />
}
} }
</tbody> </tbody>
</table> </table>
</div> </div>
<vc:ui-extension-point location="store-invoices-payments" model="@invoice.Payments" /> <vc:ui-extension-point location="store-invoices-payments" model="@invoice" />