Invoice Details: Improve payments list and print view (#4817)

Closes #4729.

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
d11n
2023-04-04 03:59:14 +02:00
committed by GitHub
parent 2bd1842da1
commit 11f05285a1
6 changed files with 140 additions and 117 deletions

View File

@@ -20,6 +20,7 @@ using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
@@ -256,7 +257,7 @@ namespace BTCPayServer.Controllers
return paymentData switch
{
BitcoinLikePaymentData b => b.Outpoint.ToString(),
LightningPaymentData l => l.Preimage,
LightningLikePaymentData l => l.Preimage?.ToString(),
_ => null
};
}

View File

@@ -23,8 +23,8 @@ namespace BTCPayServer.Models.InvoicingModels
public bool Replaced { get; set; }
public BitcoinLikePaymentData CryptoPaymentData { get; set; }
public string AdditionalInformation { get; set; }
public decimal NetworkFee { get; set; }
public string PaymentProof { get; set; }
}
public class OffChainPaymentViewModel
@@ -32,6 +32,8 @@ namespace BTCPayServer.Models.InvoicingModels
public string Crypto { get; set; }
public string BOLT11 { get; set; }
public PaymentType Type { get; set; }
public string Amount { get; set; }
public string PaymentProof { get; set; }
}
public class InvoiceDetailsModel

View File

@@ -4,10 +4,11 @@
@using BTCPayServer.Services
@inject DisplayFormatter DisplayFormatter
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
@{
PayjoinInformation payjoinIformation = null;
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance).Select(payment =>
PayjoinInformation payjoinInformation = null;
var payments = Model
.Where(entity => entity.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance)
.Select(payment =>
{
var m = new OnchainPaymentViewModel();
var onChainPaymentData = payment.GetCryptoPaymentData() as BitcoinLikePaymentData;
@@ -18,7 +19,7 @@
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
m.DepositAddress = onChainPaymentData.GetDestination();
long confirmationCount = onChainPaymentData.ConfirmationCount;
var confirmationCount = onChainPaymentData.ConfirmationCount;
var network = payment.Network as BTCPayNetwork;
if (confirmationCount >= network.MaxTrackedConfirmation)
{
@@ -28,13 +29,13 @@
{
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
}
if (onChainPaymentData?.PayjoinInformation is PayjoinInformation pj)
if (onChainPaymentData.PayjoinInformation is PayjoinInformation pj)
{
payjoinIformation = pj;
payjoinInformation = pj;
m.AdditionalInformation = "Original transaction";
}
if (payjoinIformation is PayjoinInformation &&
payjoinIformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
if (payjoinInformation is PayjoinInformation &&
payjoinInformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
{
m.AdditionalInformation = "Payjoin transaction";
}
@@ -44,23 +45,26 @@
m.Replaced = !payment.Accounted;
m.CryptoPaymentData = onChainPaymentData;
m.NetworkFee = payment.NetworkFee;
m.PaymentProof = onChainPaymentData.Outpoint.ToString();
return m;
}).Where(model => model != null);
})
.Where(model => model != null)
.ToList();
}
@if (onchainPayments.Any())
@if (payments.Any())
{
var hasNetworkFee = onchainPayments.Sum(a => a.NetworkFee) > 0;
var hasNetworkFee = payments.Sum(a => a.NetworkFee) > 0;
<section>
<h5>On-Chain Payments</h5>
<table class="table table-hover mt-3 mb-0">
<div class="invoice-payments table-responsive mt-0">
<table class="table table-hover mb-0">
<thead class="thead-inverse">
<tr>
<th>Crypto</th>
<th>Index</th>
<th class="text-nowrap">Deposit address</th>
<th>Transaction Id</th>
<th class="text-end">Amount</th>
<th class="w-75px">Crypto</th>
<th class="w-100px">Index</th>
<th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th>
@if (hasNetworkFee)
{
<th class="text-end">
@@ -71,24 +75,26 @@
</th>
}
<th class="text-end">Confirmations</th>
<th class="w-150px text-end">Amount</th>
</tr>
</thead>
<tbody>
@foreach (var payment in onchainPayments)
@foreach (var payment in payments)
{
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
<td>@payment.Crypto</td>
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
<td>
<div class="text-truncate" style="max-width:300px;" data-bs-toggle="tooltip" title="@payment.DepositAddress">@payment.DepositAddress</div>
<vc:truncate-center text="@payment.DepositAddress" classes="truncate-center-id" />
</td>
<td>
<div class="text-truncate" style="max-width:200px;" data-bs-toggle="tooltip" title="@payment.TransactionId">
<a href="@payment.TransactionLink" target="_blank" rel="noreferrer noopener">
@payment.TransactionId
</a>
</div>
<vc:truncate-center text="@payment.PaymentProof" link="@payment.TransactionLink" classes="truncate-center-id" />
</td>
@if (hasNetworkFee)
{
<td class="text-end text-nowrap">@payment.NetworkFee</td>
}
<td class="text-end">@payment.Confirmations</td>
<td class="payment-value text-end text-nowrap">
@DisplayFormatter.Currency(payment.CryptoPaymentData.GetValue(), payment.Crypto)
@if (!string.IsNullOrEmpty(payment.AdditionalInformation))
@@ -96,14 +102,10 @@
<div>(@payment.AdditionalInformation)</div>
}
</td>
@if (hasNetworkFee)
{
<td class="text-end text-nowrap">@payment.NetworkFee</td>
}
<td class="text-end">@payment.Confirmations</td>
</tr>
}
</tbody>
</table>
</div>
</section>
}

View File

@@ -1,47 +1,63 @@
@using BTCPayServer.Payments
@using BTCPayServer.Payments.Lightning
@using BTCPayServer.Services
@using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Lightning
@inject DisplayFormatter DisplayFormatter
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
@{
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)
{
return null;
}
return new OffChainPaymentViewModel
var payments = Model
.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance ||
entity.GetPaymentMethodId()?.PaymentType == LNURLPayPaymentType.Instance)
.Select(payment => payment.GetCryptoPaymentData() is LightningLikePaymentData offChainPaymentData
? new OffChainPaymentViewModel
{
Crypto = payment.Network.CryptoCode,
BOLT11 = offChainPaymentData.BOLT11,
Type = payment.GetCryptoPaymentData().GetPaymentType()
};
}).Where(model => model != null);
Type = payment.GetCryptoPaymentData().GetPaymentType(),
PaymentProof = offChainPaymentData.Preimage?.ToString(),
Amount = DisplayFormatter.Currency(offChainPaymentData.Amount.ToDecimal(LightMoneyUnit.BTC), payment.Network.CryptoCode)
}
: null)
.Where(model => model != null)
.ToList();
}
@if (offchainPayments.Any())
@if (payments.Any())
{
<section>
<h5>Off-Chain Payments</h5>
<table class="table table-hover">
<div class="invoice-payments table-responsive mt-0">
<table class="table table-hover mb-0">
<thead class="thead-inverse">
<tr>
<th class="w-150px">Crypto</th>
<th class="w-150px">Type</th>
<th>BOLT11</th>
<th class="w-75px">Crypto</th>
<th class="w-100px">Type</th>
<th class="w-175px">Destination</th>
<th class="text-nowrap">Payment Proof</th>
<th class="w-150px text-end">Amount</th>
</tr>
</thead>
<tbody>
@foreach (var payment in offchainPayments)
@foreach (var payment in payments)
{
<tr>
<td>@payment.Crypto</td>
<td>@payment.Type.ToPrettyString()</td>
<td class="text-break">@payment.BOLT11</td>
<td>
<vc:truncate-center text="@payment.BOLT11" classes="truncate-center-id" />
</td>
<td>
<vc:truncate-center text="@payment.PaymentProof" classes="truncate-center-id" />
</td>
<td class="payment-value text-end text-nowrap">
@payment.Amount
</td>
</tr>
}
</tbody>
</table>
</div>
</section>
}

View File

@@ -379,7 +379,7 @@
</div>
</div>
<h3 class="mb-0">Invoice Summary</h3>
<h3 class="mb-3">Invoice Summary</h3>
<partial name="ListInvoicesPaymentsPartial" model="(Model, true)"/>
@if (Model.Deliveries.Any())

View File

@@ -1,21 +1,26 @@
@model (InvoiceDetailsModel Invoice, bool ShowAddress)
@{ var invoice = Model.Invoice; }
@{
var invoice = Model.Invoice;
var grouped = invoice.Payments
.GroupBy(payment => payment.GetPaymentMethodId()?.PaymentType)
.Where(entities => entities.Key != null);
}
<div class="invoice-payments table-responsive">
<table class="table table-hover mt-3 mb-4">
<div class="invoice-payments table-responsive mt-0">
<table class="table table-hover mb-0">
<thead class="thead-inverse">
<tr>
<th class="text-nowrap">Payment method</th>
<th class="text-nowrap w-175px">Payment method</th>
@if (Model.ShowAddress)
{
<th>Address</th>
<th>Destination</th>
}
<th class="text-end">Rate</th>
<th class="text-end">Paid</th>
<th class="text-end">Due</th>
<th class="w-150px text-end">Rate</th>
<th class="w-150px text-end">Paid</th>
<th class="w-150px text-end">Due</th>
@if (invoice.Overpaid)
{
<th class="text-end">Overpaid</th>
<th class="w-150px text-end">Overpaid</th>
}
</tr>
</thead>
@@ -27,7 +32,7 @@
@if (Model.ShowAddress)
{
<td title="@payment.Address">
<div class="text-truncate" style="max-width:400px" data-bs-toggle="tooltip" title="@payment.Address">@payment.Address</div>
<vc:truncate-center text="@payment.Address" classes="truncate-center-id" />
</td>
}
<td class="text-nowrap text-end">@payment.Rate</td>
@@ -47,11 +52,8 @@
}
</tbody>
</table>
@{
var grouped = invoice.Payments.GroupBy(payment => payment.GetPaymentMethodId()?.PaymentType).Where(entities => entities.Key != null);
}
</div>
@foreach (var paymentGroup in grouped)
{
<partial name="@paymentGroup.Key.InvoiceViewPaymentPartialName" model="@paymentGroup.ToList()" />
}
</div>