mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
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:
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@foreach (var paymentGroup in grouped)
|
||||
{
|
||||
<partial name="@paymentGroup.Key.InvoiceViewPaymentPartialName" model="@paymentGroup.ToList()" />
|
||||
}
|
||||
</div>
|
||||
@foreach (var paymentGroup in grouped)
|
||||
{
|
||||
<partial name="@paymentGroup.Key.InvoiceViewPaymentPartialName" model="@paymentGroup.ToList()" />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user