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.Models.PaymentRequestViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Bitcoin;
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
|
using BTCPayServer.Payments.Lightning;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
@@ -256,7 +257,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return paymentData switch
|
return paymentData switch
|
||||||
{
|
{
|
||||||
BitcoinLikePaymentData b => b.Outpoint.ToString(),
|
BitcoinLikePaymentData b => b.Outpoint.ToString(),
|
||||||
LightningPaymentData l => l.Preimage,
|
LightningLikePaymentData l => l.Preimage?.ToString(),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public bool Replaced { get; set; }
|
public bool Replaced { get; set; }
|
||||||
public BitcoinLikePaymentData CryptoPaymentData { get; set; }
|
public BitcoinLikePaymentData CryptoPaymentData { get; set; }
|
||||||
public string AdditionalInformation { get; set; }
|
public string AdditionalInformation { get; set; }
|
||||||
|
|
||||||
public decimal NetworkFee { get; set; }
|
public decimal NetworkFee { get; set; }
|
||||||
|
public string PaymentProof { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OffChainPaymentViewModel
|
public class OffChainPaymentViewModel
|
||||||
@@ -32,6 +32,8 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||||||
public string Crypto { get; set; }
|
public string Crypto { get; set; }
|
||||||
public string BOLT11 { get; set; }
|
public string BOLT11 { get; set; }
|
||||||
public PaymentType Type { get; set; }
|
public PaymentType Type { get; set; }
|
||||||
|
public string Amount { get; set; }
|
||||||
|
public string PaymentProof { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvoiceDetailsModel
|
public class InvoiceDetailsModel
|
||||||
|
|||||||
@@ -4,63 +4,67 @@
|
|||||||
@using BTCPayServer.Services
|
@using BTCPayServer.Services
|
||||||
@inject DisplayFormatter DisplayFormatter
|
@inject DisplayFormatter DisplayFormatter
|
||||||
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
PayjoinInformation payjoinIformation = null;
|
PayjoinInformation payjoinInformation = null;
|
||||||
var onchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance).Select(payment =>
|
var payments = Model
|
||||||
{
|
.Where(entity => entity.GetPaymentMethodId()?.PaymentType == BitcoinPaymentType.Instance)
|
||||||
var m = new OnchainPaymentViewModel();
|
.Select(payment =>
|
||||||
var onChainPaymentData = payment.GetCryptoPaymentData() as BitcoinLikePaymentData;
|
|
||||||
if (onChainPaymentData is null)
|
|
||||||
{
|
{
|
||||||
return null;
|
var m = new OnchainPaymentViewModel();
|
||||||
}
|
var onChainPaymentData = payment.GetCryptoPaymentData() as BitcoinLikePaymentData;
|
||||||
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
if (onChainPaymentData is null)
|
||||||
m.DepositAddress = onChainPaymentData.GetDestination();
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
m.Crypto = payment.GetPaymentMethodId().CryptoCode;
|
||||||
|
m.DepositAddress = onChainPaymentData.GetDestination();
|
||||||
|
|
||||||
long confirmationCount = onChainPaymentData.ConfirmationCount;
|
var confirmationCount = onChainPaymentData.ConfirmationCount;
|
||||||
var network = payment.Network as BTCPayNetwork;
|
var network = payment.Network as BTCPayNetwork;
|
||||||
if (confirmationCount >= network.MaxTrackedConfirmation)
|
if (confirmationCount >= network.MaxTrackedConfirmation)
|
||||||
{
|
{
|
||||||
m.Confirmations = "At least " + (network.MaxTrackedConfirmation);
|
m.Confirmations = "At least " + (network.MaxTrackedConfirmation);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m.Confirmations = confirmationCount.ToString(CultureInfo.InvariantCulture);
|
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";
|
m.AdditionalInformation = "Original transaction";
|
||||||
}
|
}
|
||||||
if (payjoinIformation is PayjoinInformation &&
|
if (payjoinInformation is PayjoinInformation &&
|
||||||
payjoinIformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
|
payjoinInformation.CoinjoinTransactionHash == onChainPaymentData?.Outpoint.Hash)
|
||||||
{
|
{
|
||||||
m.AdditionalInformation = "Payjoin transaction";
|
m.AdditionalInformation = "Payjoin transaction";
|
||||||
}
|
}
|
||||||
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
|
m.TransactionId = onChainPaymentData.Outpoint.Hash.ToString();
|
||||||
m.ReceivedTime = payment.ReceivedTime;
|
m.ReceivedTime = payment.ReceivedTime;
|
||||||
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.TransactionId);
|
m.TransactionLink = string.Format(CultureInfo.InvariantCulture, payment.Network.BlockExplorerLink, m.TransactionId);
|
||||||
m.Replaced = !payment.Accounted;
|
m.Replaced = !payment.Accounted;
|
||||||
m.CryptoPaymentData = onChainPaymentData;
|
m.CryptoPaymentData = onChainPaymentData;
|
||||||
m.NetworkFee = payment.NetworkFee;
|
m.NetworkFee = payment.NetworkFee;
|
||||||
return m;
|
m.PaymentProof = onChainPaymentData.Outpoint.ToString();
|
||||||
}).Where(model => model != null);
|
return m;
|
||||||
|
})
|
||||||
|
.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>
|
<section>
|
||||||
<h5>On-Chain Payments</h5>
|
<h5>On-Chain Payments</h5>
|
||||||
<table class="table table-hover mt-3 mb-0">
|
<div class="invoice-payments table-responsive mt-0">
|
||||||
<thead class="thead-inverse">
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="thead-inverse">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Crypto</th>
|
<th class="w-75px">Crypto</th>
|
||||||
<th>Index</th>
|
<th class="w-100px">Index</th>
|
||||||
<th class="text-nowrap">Deposit address</th>
|
<th class="w-175px">Destination</th>
|
||||||
<th>Transaction Id</th>
|
<th class="text-nowrap">Payment Proof</th>
|
||||||
<th class="text-end">Amount</th>
|
|
||||||
@if (hasNetworkFee)
|
@if (hasNetworkFee)
|
||||||
{
|
{
|
||||||
<th class="text-end">
|
<th class="text-end">
|
||||||
@@ -71,24 +75,26 @@
|
|||||||
</th>
|
</th>
|
||||||
}
|
}
|
||||||
<th class="text-end">Confirmations</th>
|
<th class="text-end">Confirmations</th>
|
||||||
|
<th class="w-150px text-end">Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var payment in onchainPayments)
|
@foreach (var payment in payments)
|
||||||
{
|
{
|
||||||
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
|
<tr style="@(payment.Replaced ? "text-decoration: line-through" : "")">
|
||||||
<td>@payment.Crypto</td>
|
<td>@payment.Crypto</td>
|
||||||
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
|
<td>@(payment.CryptoPaymentData.KeyPath?.ToString()?? "Unknown")</td>
|
||||||
<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>
|
||||||
<td>
|
<td>
|
||||||
<div class="text-truncate" style="max-width:200px;" data-bs-toggle="tooltip" title="@payment.TransactionId">
|
<vc:truncate-center text="@payment.PaymentProof" link="@payment.TransactionLink" classes="truncate-center-id" />
|
||||||
<a href="@payment.TransactionLink" target="_blank" rel="noreferrer noopener">
|
|
||||||
@payment.TransactionId
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
</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">
|
<td class="payment-value text-end text-nowrap">
|
||||||
@DisplayFormatter.Currency(payment.CryptoPaymentData.GetValue(), payment.Crypto)
|
@DisplayFormatter.Currency(payment.CryptoPaymentData.GetValue(), payment.Crypto)
|
||||||
@if (!string.IsNullOrEmpty(payment.AdditionalInformation))
|
@if (!string.IsNullOrEmpty(payment.AdditionalInformation))
|
||||||
@@ -96,14 +102,10 @@
|
|||||||
<div>(@payment.AdditionalInformation)</div>
|
<div>(@payment.AdditionalInformation)</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
@if (hasNetworkFee)
|
|
||||||
{
|
|
||||||
<td class="text-end text-nowrap">@payment.NetworkFee</td>
|
|
||||||
}
|
|
||||||
<td class="text-end">@payment.Confirmations</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,63 @@
|
|||||||
@using BTCPayServer.Payments
|
@using BTCPayServer.Payments
|
||||||
@using BTCPayServer.Payments.Lightning
|
@using BTCPayServer.Payments.Lightning
|
||||||
|
@using BTCPayServer.Services
|
||||||
|
@using BTCPayServer.Components.TruncateCenter
|
||||||
|
@using BTCPayServer.Lightning
|
||||||
|
@inject DisplayFormatter DisplayFormatter
|
||||||
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
@model IEnumerable<BTCPayServer.Services.Invoices.PaymentEntity>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var offchainPayments = Model.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance || entity.GetPaymentMethodId()?.PaymentType == LNURLPayPaymentType.Instance).Select(payment =>
|
var payments = Model
|
||||||
{
|
.Where(entity => entity.GetPaymentMethodId()?.PaymentType == LightningPaymentType.Instance ||
|
||||||
var offChainPaymentData = payment.GetCryptoPaymentData() as LightningLikePaymentData;
|
entity.GetPaymentMethodId()?.PaymentType == LNURLPayPaymentType.Instance)
|
||||||
if (offChainPaymentData is null)
|
.Select(payment => payment.GetCryptoPaymentData() is LightningLikePaymentData offChainPaymentData
|
||||||
{
|
? new OffChainPaymentViewModel
|
||||||
return null;
|
{
|
||||||
}
|
Crypto = payment.Network.CryptoCode,
|
||||||
return new OffChainPaymentViewModel
|
BOLT11 = offChainPaymentData.BOLT11,
|
||||||
{
|
Type = payment.GetCryptoPaymentData().GetPaymentType(),
|
||||||
Crypto = payment.Network.CryptoCode,
|
PaymentProof = offChainPaymentData.Preimage?.ToString(),
|
||||||
BOLT11 = offChainPaymentData.BOLT11,
|
Amount = DisplayFormatter.Currency(offChainPaymentData.Amount.ToDecimal(LightMoneyUnit.BTC), payment.Network.CryptoCode)
|
||||||
Type = payment.GetCryptoPaymentData().GetPaymentType()
|
}
|
||||||
};
|
: null)
|
||||||
}).Where(model => model != null);
|
.Where(model => model != null)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (offchainPayments.Any())
|
@if (payments.Any())
|
||||||
{
|
{
|
||||||
<section>
|
<section>
|
||||||
<h5>Off-Chain Payments</h5>
|
<h5>Off-Chain Payments</h5>
|
||||||
<table class="table table-hover">
|
<div class="invoice-payments table-responsive mt-0">
|
||||||
<thead class="thead-inverse">
|
<table class="table table-hover mb-0">
|
||||||
<tr>
|
<thead class="thead-inverse">
|
||||||
<th class="w-150px">Crypto</th>
|
|
||||||
<th class="w-150px">Type</th>
|
|
||||||
<th>BOLT11</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var payment in offchainPayments)
|
|
||||||
{
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>@payment.Crypto</td>
|
<th class="w-75px">Crypto</th>
|
||||||
<td>@payment.Type.ToPrettyString()</td>
|
<th class="w-100px">Type</th>
|
||||||
<td class="text-break">@payment.BOLT11</td>
|
<th class="w-175px">Destination</th>
|
||||||
|
<th class="text-nowrap">Payment Proof</th>
|
||||||
|
<th class="w-150px text-end">Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
@foreach (var payment in payments)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@payment.Crypto</td>
|
||||||
|
<td>@payment.Type.ToPrettyString()</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>
|
</section>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mb-0">Invoice Summary</h3>
|
<h3 class="mb-3">Invoice Summary</h3>
|
||||||
<partial name="ListInvoicesPaymentsPartial" model="(Model, true)"/>
|
<partial name="ListInvoicesPaymentsPartial" model="(Model, true)"/>
|
||||||
|
|
||||||
@if (Model.Deliveries.Any())
|
@if (Model.Deliveries.Any())
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
@model (InvoiceDetailsModel Invoice, bool ShowAddress)
|
@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">
|
<div class="invoice-payments table-responsive mt-0">
|
||||||
<table class="table table-hover mt-3 mb-4">
|
<table class="table table-hover mb-0">
|
||||||
<thead class="thead-inverse">
|
<thead class="thead-inverse">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-nowrap">Payment method</th>
|
<th class="text-nowrap w-175px">Payment method</th>
|
||||||
@if (Model.ShowAddress)
|
@if (Model.ShowAddress)
|
||||||
{
|
{
|
||||||
<th>Address</th>
|
<th>Destination</th>
|
||||||
}
|
}
|
||||||
<th class="text-end">Rate</th>
|
<th class="w-150px text-end">Rate</th>
|
||||||
<th class="text-end">Paid</th>
|
<th class="w-150px text-end">Paid</th>
|
||||||
<th class="text-end">Due</th>
|
<th class="w-150px text-end">Due</th>
|
||||||
@if (invoice.Overpaid)
|
@if (invoice.Overpaid)
|
||||||
{
|
{
|
||||||
<th class="text-end">Overpaid</th>
|
<th class="w-150px text-end">Overpaid</th>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -27,7 +32,7 @@
|
|||||||
@if (Model.ShowAddress)
|
@if (Model.ShowAddress)
|
||||||
{
|
{
|
||||||
<td title="@payment.Address">
|
<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>
|
||||||
}
|
}
|
||||||
<td class="text-nowrap text-end">@payment.Rate</td>
|
<td class="text-nowrap text-end">@payment.Rate</td>
|
||||||
@@ -47,11 +52,8 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
|
@foreach (var paymentGroup in grouped)
|
||||||
|
{
|
||||||
|
<partial name="@paymentGroup.Key.InvoiceViewPaymentPartialName" model="@paymentGroup.ToList()" />
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user