mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Improve Payment Request view (#2748)
* Improve Payment Request view Closes #2747. * Fix payment request invoice listing condition
This commit is contained in:
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user