Receipt fixes and improvements (#5505)

* Fix additional div

* Don't show payment number if there is only one

* Bump max-width to prevent wrapping in top container

* Fix colspan

* Re-add POS data

Closes #5498.

* Right-align amounts

* Re-order

* Don't show redundant receive date if there is only one payment

* Table improvements

* Unify crypto amount display

* More formatting improvements

* Only show Subtotal if there are calculations applicable to it

* Making margin on the bottom smaller to reduce expansion on Bitcoinize machines

---------

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>
This commit is contained in:
d11n
2023-11-23 19:05:08 +01:00
committed by GitHub
parent 62865d7d88
commit c8b9a425b8
5 changed files with 137 additions and 122 deletions

View File

@@ -254,7 +254,7 @@ namespace BTCPayServer.Controllers
Amount = paymentEntity.PaidAmount.Gross, Amount = paymentEntity.PaidAmount.Gross,
Paid = paymentEntity.InvoicePaidAmount.Net, Paid = paymentEntity.InvoicePaidAmount.Net,
ReceivedDate = paymentEntity.ReceivedTime.DateTime, ReceivedDate = paymentEntity.ReceivedTime.DateTime,
AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency, DisplayFormatter.CurrencyFormat.None), AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency),
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, i.Currency, DisplayFormatter.CurrencyFormat.Symbol), PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol), RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
PaymentMethod = paymentMethodId.ToPrettyString(), PaymentMethod = paymentMethodId.ToPrettyString(),

View File

@@ -135,7 +135,7 @@ namespace BTCPayServer.PaymentRequest
Amount = paymentEntity.PaidAmount.Gross, Amount = paymentEntity.PaidAmount.Gross,
Paid = paymentEntity.InvoicePaidAmount.Net, Paid = paymentEntity.InvoicePaidAmount.Net,
ReceivedDate = paymentEntity.ReceivedTime.DateTime, ReceivedDate = paymentEntity.ReceivedTime.DateTime,
AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency, DisplayFormatter.CurrencyFormat.None), AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency),
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol), PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol), RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
PaymentMethod = paymentMethodId.ToPrettyString(), PaymentMethod = paymentMethodId.ToPrettyString(),

View File

@@ -31,7 +31,7 @@
</script> </script>
} }
<style> <style>
#InvoiceReceipt { --wrap-max-width: 720px; } #InvoiceReceipt { --wrap-max-width: 768px; }
#InvoiceSummary { gap: var(--btcpay-space-l); } #InvoiceSummary { gap: var(--btcpay-space-l); }
#PaymentDetails table tbody tr:first-child td { padding-top: 1rem; } #PaymentDetails table tbody tr:first-child td { padding-top: 1rem; }
#PaymentDetails table tbody:not(:last-child) tr:last-child > th,td { padding-bottom: 1rem; } #PaymentDetails table tbody:not(:last-child) tr:last-child > th,td { padding-bottom: 1rem; }
@@ -86,7 +86,6 @@
</div> </div>
} }
</div> </div>
</div>
@if (isProcessing) @if (isProcessing)
{ {
@@ -122,7 +121,7 @@
<tr> <tr>
<td class="date-col">@payment.ReceivedDate.ToBrowserDate()</td> <td class="date-col">@payment.ReceivedDate.ToBrowserDate()</td>
<td class="amount-col">@payment.PaidFormatted</td> <td class="amount-col">@payment.PaidFormatted</td>
<td class="amount-col">@payment.AmountFormatted @payment.PaymentMethod</td> <td class="amount-col">@payment.AmountFormatted</td>
</tr> </tr>
@if (!string.IsNullOrEmpty(payment.Destination)) @if (!string.IsNullOrEmpty(payment.Destination))
{ {

View File

@@ -10,6 +10,8 @@
var isFreeInvoice = (Model.Status == InvoiceStatus.New && Model.Amount == 0); var isFreeInvoice = (Model.Status == InvoiceStatus.New && Model.Amount == 0);
var isSettled = Model.Status == InvoiceStatus.Settled; var isSettled = Model.Status == InvoiceStatus.Settled;
} }
<!DOCTYPE html>
<html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -70,124 +72,138 @@
</style> </style>
</head> </head>
<body style="margin:0; padding:0; background-color:#fff"> <body class="m-0 p-0 bg-white">
<center> <center>
<div> <partial name="_StoreHeader" model="(Model.StoreName, Model.LogoFileId)" />
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData) { { "Margin", "mb-4" } })" /> <div id="InvoiceSummary" style="max-width:600px">
@if (isProcessing)
<div class="justify-content-center"> {
<partial name="_StoreHeader" model="(Model.StoreName, Model.LogoFileId)" /> <div class="lead text-center fw-semibold" id="invoice-processing">
<div id="InvoiceSummary" class="bg-tile"> The invoice has detected a payment but is still waiting to be settled.
@if (isProcessing)
{
<div class="lead text-center fw-semibold" id="invoice-processing">
The invoice has detected a payment but is still waiting to be settled.
</div>
}
else if (!isSettled)
{
<div class="lead text-center fw-semibold" id="invoice-unsettled">
The invoice is not settled.
</div>
}
else
{
<div id="PaymentDetails" class="bg-tile">
<div class="table-responsive my-0">
<table class="table table-borderless table-sm small my-0" style="max-width: 500px">
<thead>
<tr>
<th class="fw-normal text-secondary"></th>
<th class="fw-normal text-secondary"></th>
</tr>
</thead>
<tbody>
<tr>
<td class="fw-normal text-nowrap text-secondary">Paid</td>
<td>@DisplayFormatter.Currency(Model.Amount, Model.Currency, DisplayFormatter.CurrencyFormat.Symbol)</td>
</tr>
<tr>
<td class="fw-normal text-nowrap text-secondary">Date/Time</td>
<td>@Model.Timestamp.ToBrowserDate()</td>
</tr>
@if (!string.IsNullOrEmpty(Model.OrderId))
{
<tr>
<td class="fw-normal text-nowrap text-secondary">Order ID</td>
<td>@Model.OrderId</td>
</tr>
}
@if (Model.Payments?.Any() is true)
{
@for (int i = 0; i < Model.Payments.Count; i++)
{
var payment = Model.Payments[i];
<tr>
<td colspan="2" class="fw-normal text-nowrap text-secondary">Payment @(i + 1)</td>
</tr>
<tr>
<td class="fw-normal text-nowrap text-secondary">Received</td>
<td>@payment.ReceivedDate.ToBrowserDate()</td>
</tr>
<tr>
<td class="fw-normal text-nowrap text-secondary"></td>
<td colspan="2">@payment.AmountFormatted @payment.PaymentMethod</td>
</tr>
<tr>
<td class="fw-normal text-nowrap text-secondary"></td>
<td colspan="2">@payment.PaidFormatted</td>
</tr>
<tr>
<td class="fw-normal text-nowrap text-secondary">Rate</td>
<td colspan="2">@payment.RateFormatted</td>
</tr>
@if (!string.IsNullOrEmpty(payment.Destination))
{
<tr>
<td class="fw-normal text-nowrap text-secondary">Destination</td>
<td style="word-break:break-all">@payment.Destination</td>
</tr>
}
@if (!string.IsNullOrEmpty(payment.PaymentProof))
{
<tr>
<td class="fw-normal text-nowrap text-secondary">Pay Proof</td>
<td style="word-break:break-all">@payment.PaymentProof</td>
</tr>
}
}
}
</tbody>
<tfoot>
<tr>
<th class="fw-normal text-secondary"></th>
<th class="fw-normal text-secondary"></th>
</tr>
</tfoot>
</table>
</div>
</div>
if (Model.ReceiptOptions.ShowQR is true)
{
<vc:qr-code style="width:" data="@Context.Request.GetCurrentUrl()" size="128"></vc:qr-code>
}
}
</div>
</div> </div>
</div> }
else if (!isSettled)
<footer class="store-footer" style="padding: 0.5rem;"> {
<a class="store-powered-by" style="color: #000;" href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener"> <div class="lead text-center fw-semibold" id="invoice-unsettled">
Powered by <partial name="_StoreFooterLogo" /> The invoice is not settled.
</a> </div>
</footer> }
</center> else
{
<div id="PaymentDetails">
<div class="my-2 text-center small">
@if (!string.IsNullOrEmpty(Model.OrderId))
{
<div>Order ID: @Model.OrderId</div>
}
@Model.Timestamp.ToBrowserDate()
</div>
<table class="table table-borderless table-sm small my-0">
<tr>
<td class="text-nowrap text-secondary">Total</td>
<td class="text-end fw-semibold">@DisplayFormatter.Currency(Model.Amount, Model.Currency, DisplayFormatter.CurrencyFormat.Symbol)</td>
</tr>
<tr>
<td colspan="2"><hr class="w-100 my-0"/></td>
</tr>
@if (Model.AdditionalData?.Any() is true &&
(Model.AdditionalData.ContainsKey("Cart") || Model.AdditionalData.ContainsKey("Discount") || Model.AdditionalData.ContainsKey("Tip")))
{
@if (Model.AdditionalData.ContainsKey("Cart"))
{
@foreach (var (key, value) in (Dictionary<string, object>)Model.AdditionalData["Cart"])
{
<tr>
<td class="text-secondary">@key</td>
<td class="text-end">@value</td>
</tr>
}
}
@if (Model.AdditionalData.ContainsKey("Subtotal"))
{
<tr>
<td class="text-secondary">Subtotal</td>
<td class="text-end">@Model.AdditionalData["Subtotal"]</td>
</tr>
}
@if (Model.AdditionalData.ContainsKey("Discount"))
{
<tr>
<td class="text-secondary">Discount</td>
<td class="text-end">@Model.AdditionalData["Discount"]</td>
</tr>
}
@if (Model.AdditionalData.ContainsKey("Tip"))
{
<tr>
<td class="text-secondary">Tip</td>
<td class="text-end">@Model.AdditionalData["Tip"]</td>
</tr>
}
<tr>
<td colspan="2"><hr class="w-100 my-0"/></td>
</tr>
}
@if (Model.Payments?.Any() is true)
{
@for (var i = 0; i < Model.Payments.Count; i++)
{
var payment = Model.Payments[i];
@if (Model.Payments.Count > 1)
{
<tr>
<td colspan="2" class="text-nowrap text-secondary">Payment @(i + 1)</td>
</tr>
<tr>
<td class="text-nowrap">Received</td>
<td>@payment.ReceivedDate.ToBrowserDate()</td>
</tr>
}
<tr>
<td class="text-nowrap text-secondary">@(Model.Payments.Count == 1 ? "Paid" : "")</td>
<td class="text-end">@payment.AmountFormatted</td>
</tr>
<tr>
<td colspan="2" class="text-end">@payment.PaidFormatted</td>
</tr>
<tr>
<td class="text-nowrap text-secondary">Rate</td>
<td class="text-end">@payment.RateFormatted</td>
</tr>
@if (!string.IsNullOrEmpty(payment.Destination))
{
<tr>
<td class="text-nowrap text-secondary">Destination</td>
<td class="text-break">@payment.Destination</td>
</tr>
}
@if (!string.IsNullOrEmpty(payment.PaymentProof))
{
<tr>
<td class="text-nowrap text-secondary">Pay Proof</td>
<td class="text-break">@payment.PaymentProof</td>
</tr>
}
}
<tr>
<td colspan="2"><hr class="w-100 my-0"/></td>
</tr>
}
</table>
</div>
if (Model.ReceiptOptions.ShowQR is true)
{
<vc:qr-code data="@Context.Request.GetCurrentUrl()" size="128" />
}
}
</div>
<div class="store-footer p-3">
<a class="store-powered-by" style="color:#000;">Powered by <partial name="_StoreFooterLogo" /></a>
</div>
<hr class="w-100 my-0 bg-none"/>
</center>
</body> </body>
<script> <script>
window.print(); window.print();
</script> </script>
</html>

View File

@@ -299,7 +299,7 @@
<tr> <tr>
<td class="text-break"><vc:truncate-center text="@payment.Id" link="@payment.Link" padding="7" classes="truncate-center-id" /></td> <td class="text-break"><vc:truncate-center text="@payment.Id" link="@payment.Link" padding="7" classes="truncate-center-id" /></td>
<td class="amount-col">@payment.PaidFormatted</td> <td class="amount-col">@payment.PaidFormatted</td>
<td class="text-end text-nowrap">@payment.AmountFormatted @payment.PaymentMethod</td> <td class="text-end text-nowrap">@payment.AmountFormatted</td>
</tr> </tr>
} }
} }