Improve Payment Request view (#2748)

* Improve Payment Request view

Closes #2747.

* Fix payment request invoice listing condition
This commit is contained in:
d11n
2021-08-04 09:13:33 +02:00
committed by GitHub
parent 5bf1161884
commit 80086d76a8
5 changed files with 130 additions and 97 deletions

View File

@@ -159,6 +159,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public DateTime ReceivedDate { get; set; } public DateTime ReceivedDate { get; set; }
public string Link { get; set; } public string Link { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Destination { get; set; }
} }
} }
} }

View File

@@ -102,6 +102,44 @@ namespace BTCPayServer.PaymentRequest
Invoices = invoices.Select(entity => Invoices = invoices.Select(entity =>
{ {
var state = entity.GetInvoiceState(); var state = entity.GetInvoiceState();
var payments = entity
.GetPayments(true)
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId);
var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
var amount = paymentData.GetValue();
var rate = paymentMethod.Rate;
var paid = (amount - paymentEntity.NetworkFee) * rate;
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
{
Amount = amount,
Paid = paid,
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
PaidFormatted = _currencies.FormatCurrency(paid, blob.Currency),
RateFormatted = _currencies.FormatCurrency(rate, blob.Currency),
PaymentMethod = paymentMethodId.ToPrettyString(),
Link = link,
Id = txId,
Destination = paymentData.GetDestination()
};
})
.Where(payment => payment != null)
.ToList();
if (state.Status == InvoiceStatusLegacy.Invalid ||
state.Status == InvoiceStatusLegacy.Expired && !payments.Any())
return null;
return new ViewPaymentRequestViewModel.PaymentRequestInvoice return new ViewPaymentRequestViewModel.PaymentRequestInvoice
{ {
Id = entity.Id, Id = entity.Id,
@@ -111,40 +149,11 @@ namespace BTCPayServer.PaymentRequest
ExpiryDate = entity.ExpirationTime.DateTime, ExpiryDate = entity.ExpirationTime.DateTime,
State = state, State = state,
StateFormatted = state.ToString(), StateFormatted = state.ToString(),
Payments = entity Payments = payments
.GetPayments(true)
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId);
var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
var amount = paymentData.GetValue();
var rate = paymentMethod.Rate;
var paid = (amount - paymentEntity.NetworkFee) * rate;
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
{
Amount = amount,
Paid = paid,
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
PaidFormatted = _currencies.FormatCurrency(paid, blob.Currency),
RateFormatted = _currencies.FormatCurrency(rate, blob.Currency),
PaymentMethod = paymentMethodId.ToPrettyString(),
Link = link,
Id = txId
};
})
.Where(payment => payment != null)
.ToList()
}; };
}).ToList() })
.Where(invoice => invoice != null)
.ToList()
}; };
} }

View File

@@ -9,27 +9,27 @@
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
var theme = await _settingsRepository.GetTheme(); var theme = await _settingsRepository.GetTheme();
string StatusTextClass(InvoiceState state) string StatusClass(InvoiceState state)
{ {
switch (state.Status.ToModernStatus()) switch (state.Status.ToModernStatus())
{ {
case InvoiceStatus.Settled: case InvoiceStatus.Settled:
case InvoiceStatus.Processing: case InvoiceStatus.Processing:
return "text-success"; return "success";
case InvoiceStatus.Expired: case InvoiceStatus.Expired:
switch (state.ExceptionStatus) switch (state.ExceptionStatus)
{ {
case InvoiceExceptionStatus.PaidLate: case InvoiceExceptionStatus.PaidLate:
case InvoiceExceptionStatus.PaidPartial: case InvoiceExceptionStatus.PaidPartial:
case InvoiceExceptionStatus.PaidOver: case InvoiceExceptionStatus.PaidOver:
return "text-warning"; return "warning";
default: default:
return "text-danger"; return "danger";
} }
case InvoiceStatus.Invalid: case InvoiceStatus.Invalid:
return "text-danger"; return "danger";
default: default:
return "text-warning"; return "warning";
} }
} }
} }
@@ -56,6 +56,11 @@
@*We need to make sure btcpay.js is not bundled, else it will not work if there is a RootPath*@ @*We need to make sure btcpay.js is not bundled, else it will not work if there is a RootPath*@
<script src="~/modal/btcpay.js" asp-append-version="true"></script> <script src="~/modal/btcpay.js" asp-append-version="true"></script>
@Safe.Raw(Model.EmbeddedCSS) @Safe.Raw(Model.EmbeddedCSS)
<style>
.invoice { margin-top: var(--btcpay-space-s); }
.invoice + .invoice { margin-top: var(--btcpay-space-m); }
.invoice .badge { font-size: var(--btcpay-font-size-s); }
</style>
<noscript> <noscript>
<style> <style>
.hide-when-js, [v-cloak] { display: block !important; } .hide-when-js, [v-cloak] { display: block !important; }
@@ -241,30 +246,32 @@
} }
else else
{ {
<table class="table my-0"> @foreach (var invoice in Model.Invoices)
<thead> {
<tr class="table-borderless"> <table class="invoice table">
<th class="fw-normal text-secondary" scope="col">Invoice Id</th> <thead>
<th class="fw-normal text-secondary w-175px">Expiry</th> <tr class="table-borderless">
<th class="fw-normal text-secondary text-end w-125px">Amount</th> <th class="fw-normal text-secondary w-350px" scope="col">Invoice Id</th>
<th class="fw-normal text-secondary text-end w-125px"></th> <th class="fw-normal text-secondary w-175px">Expiry</th>
<th class="fw-normal text-secondary text-end">Status</th> <th class="fw-normal text-secondary text-end w-125px">Amount</th>
</tr> <th class="fw-normal text-secondary text-end w-125px"></th>
</thead> <th class="fw-normal text-secondary text-end">Status</th>
<tbody> </tr>
@foreach (var invoice in Model.Invoices) </thead>
{ <tbody>
<tr> <tr class="table-borderless table-light">
<td>@invoice.Id</td> <td>@invoice.Id</td>
<td>@invoice.ExpiryDate.ToString("g")</td> <td>@invoice.ExpiryDate.ToString("g")</td>
<td class="text-end">@invoice.AmountFormatted</td> <td class="text-end">@invoice.AmountFormatted</td>
<td class="text-end"></td> <td class="text-end"></td>
<td class="text-end text-print-default @StatusTextClass(invoice.State)">@invoice.StateFormatted</td> <td class="text-end text-print-default">
<span class="badge bg-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
</td>
</tr> </tr>
if (invoice.Payments != null && invoice.Payments.Any()) @if (invoice.Payments != null && invoice.Payments.Any())
{ {
<tr class="table-borderless table-light"> <tr class="table-borderless table-light">
<th class="fw-normal text-secondary ps-3">Transaction Id</th> <th class="fw-normal text-secondary">Destination</th>
<th class="fw-normal text-secondary">Received</th> <th class="fw-normal text-secondary">Received</th>
<th class="fw-normal text-secondary text-end">Paid</th> <th class="fw-normal text-secondary text-end">Paid</th>
<th class="fw-normal text-secondary text-end">Rate</th> <th class="fw-normal text-secondary text-end">Rate</th>
@@ -273,26 +280,30 @@
@foreach (var payment in invoice.Payments) @foreach (var payment in invoice.Payments)
{ {
<tr class="table-borderless table-light"> <tr class="table-borderless table-light">
<td class="ps-3 text-break"> <td class="text-break"><code>@payment.Destination</code></td>
@if (!string.IsNullOrEmpty(payment.Link))
{
<a href="@payment.Link" class="text-print-default" rel="noreferrer noopener" target="_blank">@payment.Id</a>
}
else
{
<span>@payment.Id</span>
}
</td>
<td>@payment.ReceivedDate.ToString("g")</td> <td>@payment.ReceivedDate.ToString("g")</td>
<td class="text-end">@payment.PaidFormatted</td> <td class="text-end">@payment.PaidFormatted</td>
<td class="text-end">@payment.RateFormatted</td> <td class="text-end">@payment.RateFormatted</td>
<td class="text-end text-nowrap">@payment.Amount @payment.PaymentMethod</td> <td class="text-end text-nowrap">@payment.Amount @payment.PaymentMethod</td>
</tr> </tr>
<tr class="table-borderless table-light">
<td class="fw-normal" colspan="5">
<span class="text-secondary">Transaction Id:</span>
@if (!string.IsNullOrEmpty(payment.Link))
{
<a href="@payment.Link" class="text-print-default text-break" rel="noreferrer noopener" target="_blank">@payment.Id</a>
}
else
{
<span class="text-break">@payment.Id</span>
}
</td>
</tr>
} }
} }
} </tbody>
</tbody> </table>
</table> }
} }
</noscript> </noscript>
@@ -300,10 +311,10 @@
<p class="text-muted">No payments made yet.</p> <p class="text-muted">No payments made yet.</p>
</template> </template>
<template v-else> <template v-else>
<table class="table my-0"> <table v-for="invoice of srvModel.invoices" :key="invoice.id" class="invoice table">
<thead> <thead>
<tr class="table-borderless"> <tr class="table-borderless">
<th class="fw-normal text-secondary" scope="col">Invoice Id</th> <th class="fw-normal text-secondary w-350px" scope="col">Invoice Id</th>
<th class="fw-normal text-secondary w-175px">Expiry</th> <th class="fw-normal text-secondary w-175px">Expiry</th>
<th class="fw-normal text-secondary text-end w-125px">Amount</th> <th class="fw-normal text-secondary text-end w-125px">Amount</th>
<th class="fw-normal text-secondary text-end w-125px"></th> <th class="fw-normal text-secondary text-end w-125px"></th>
@@ -311,32 +322,38 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<template v-for="invoice of srvModel.invoices" :key="invoice.id"> <tr class="table-borderless table-light">
<tr> <td>{{invoice.id}}</td>
<td>{{invoice.id}}</td> <td v-text="formatDate(invoice.expiryDate)"></td>
<td v-text="formatDate(invoice.expiryDate)"></td> <td class="text-end">{{invoice.amountFormatted}}</td>
<td class="text-end">{{invoice.amountFormatted}}</td> <td class="text-end"></td>
<td class="text-end"></td> <td class="text-end text-print-default">
<td class="text-end text-print-default" :class="statusTextClass(invoice.stateFormatted)">{{invoice.stateFormatted}}</td> <span class="badge" :class="`bg-${statusClass(invoice.stateFormatted)}`">{{invoice.stateFormatted}}</span>
</td>
</tr>
<template v-if="invoice.payments && invoice.payments.length > 0">
<tr class="table-borderless table-light">
<th class="fw-normal text-secondary">Destination</th>
<th class="fw-normal text-secondary">Received</th>
<th class="fw-normal text-secondary text-end">Paid</th>
<th class="fw-normal text-secondary text-end">Rate</th>
<th class="fw-normal text-secondary text-end">Payment</th>
</tr> </tr>
<template v-if="invoice.payments && invoice.payments.length > 0"> <template v-for="payment of invoice.payments">
<tr class="table-borderless table-light"> <tr class="table-borderless table-light">
<th class="fw-normal text-secondary ps-3">Transaction Id</th> <td class="text-break"><code>{{payment.destination}}</code></td>
<th class="fw-normal text-secondary">Received</th>
<th class="fw-normal text-secondary text-end">Paid</th>
<th class="fw-normal text-secondary text-end">Rate</th>
<th class="fw-normal text-secondary text-end">Payment</th>
</tr>
<tr v-for="payment of invoice.payments" class="table-borderless table-light">
<td class="ps-3 text-break">
<a v-if="payment.link" :href="payment.link" class="text-print-default" target="_blank" rel="noreferrer noopener">{{payment.id}}</a>
<span v-else>{{payment.id}}</span>
</td>
<td v-text="formatDate(payment.receivedDate)"></td> <td v-text="formatDate(payment.receivedDate)"></td>
<td class="text-end">{{payment.paidFormatted}}</td> <td class="text-end">{{payment.paidFormatted}}</td>
<td class="text-end">{{payment.rateFormatted}}</td> <td class="text-end">{{payment.rateFormatted}}</td>
<td class="text-end text-nowrap">{{payment.amount.noExponents()}} {{payment.paymentMethod}}</td> <td class="text-end text-nowrap">{{payment.amount.noExponents()}} {{payment.paymentMethod}}</td>
</tr> </tr>
<tr class="table-borderless table-light">
<td class="fw-normal" colspan="5">
<span class="text-secondary">Transaction Id:</span>
<a v-if="payment.link" :href="payment.link" class="text-print-default" target="_blank" rel="noreferrer noopener">{{payment.id}}</a>
<span v-else>{{payment.id}}</span>
</td>
</tr>
</template> </template>
</template> </template>
</tbody> </tbody>

View File

@@ -217,6 +217,12 @@ h2 small .fa-question-circle-o {
.w-150px { width: 150px; } .w-150px { width: 150px; }
.w-175px { width: 175px; } .w-175px { width: 175px; }
.w-200px { width: 200px; } .w-200px { width: 200px; }
.w-225px { width: 225px; }
.w-250px { width: 250px; }
.w-275px { width: 275px; }
.w-300px { width: 300px; }
.w-325px { width: 325px; }
.w-350px { width: 350px; }
/* Print */ /* Print */
@media print { @media print {
@@ -224,7 +230,7 @@ h2 small .fa-question-circle-o {
.table th { .table th {
background: transparent; background: transparent;
} }
.jumbotron { .bg-tile.h-100.p-3 {
padding: 1rem 0 !important; padding: 1rem 0 !important;
} }
.text-print-default { .text-print-default {

View File

@@ -108,26 +108,26 @@ addLoadEvent(function (ev) {
this.pay(); this.pay();
} }
}, },
statusTextClass: function (state) { statusClass: function (state) {
var [, status,, exceptionStatus] = state.match(/(\w*)\s?(\((\w*)\))?/) || []; var [, status,, exceptionStatus] = state.match(/(\w*)\s?(\((\w*)\))?/) || [];
switch (status) { switch (status) {
case "confirmed": case "confirmed":
case "complete": case "complete":
case "paid": case "paid":
return "text-success"; return "success";
case "expired": case "expired":
switch (exceptionStatus) { switch (exceptionStatus) {
case "paidLate": case "paidLate":
case "paidPartial": case "paidPartial":
case "paidOver": case "paidOver":
return "text-warning"; return "warning";
default: default:
return "text-danger"; return "danger";
} }
case "invalid": case "invalid":
return "text-danger"; return "danger";
default: default:
return "text-warning"; return "warning";
} }
} }
}, },