mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 06:21:44 +01:00
Receipt: Add payment proof (#4782)
* Receipt: Add payment proof Closes #4685. * shice * Add truncate-center component * Improve view * Hide button and link when printed * Describe component * Remove transaction ID from UI * Remove modification to interface --------- Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
c53d5272d6
commit
de9ac9fd43
7 changed files with 166 additions and 31 deletions
17
BTCPayServer/Components/TruncateCenter/Default.cshtml
Normal file
17
BTCPayServer/Components/TruncateCenter/Default.cshtml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@model BTCPayServer.Components.TruncateCenter.TruncateCenterViewModel
|
||||||
|
<span class="truncate-center @Model.Classes">
|
||||||
|
<span class="truncate-center-truncated" @(Model.Truncated != Model.Text ? $"data-bs-toggle=tooltip title={Model.Text}" : "")>@Model.Truncated</span>
|
||||||
|
<span class="truncate-center-text">@Model.Text</span>
|
||||||
|
@if (Model.Copy)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-link p-0" data-clipboard="@Model.Text">
|
||||||
|
<vc:icon symbol="copy" />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Link))
|
||||||
|
{
|
||||||
|
<a href="@Model.Link" rel="noreferrer noopener" target="_blank">
|
||||||
|
<vc:icon symbol="info" />
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</span>
|
29
BTCPayServer/Components/TruncateCenter/TruncateCenter.cs
Normal file
29
BTCPayServer/Components/TruncateCenter/TruncateCenter.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Components.TruncateCenter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Truncates long strings in the center with ellipsis: Turns e.g. a BOLT11 into "lnbcrt7…q2ns60y"
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The full text, e.g. a Bitcoin address or BOLT11</param>
|
||||||
|
/// <param name="link">Optional link, e.g. a block explorer URL</param>
|
||||||
|
/// <param name="classes">Optional additional CSS classes</param>
|
||||||
|
/// <param name="padding">The number of characters to show on each side</param>
|
||||||
|
/// <param name="copy">Display a copy button</param>
|
||||||
|
/// <returns>HTML with truncated string</returns>
|
||||||
|
public class TruncateCenter : ViewComponent
|
||||||
|
{
|
||||||
|
public IViewComponentResult Invoke(string text, string link = null, string classes = null, int padding = 7, bool copy = true)
|
||||||
|
{
|
||||||
|
var vm = new TruncateCenterViewModel
|
||||||
|
{
|
||||||
|
Classes = classes,
|
||||||
|
Padding = padding,
|
||||||
|
Copy = copy,
|
||||||
|
Text = text,
|
||||||
|
Link = link,
|
||||||
|
Truncated = text.Length > 2 * padding ? $"{text[..padding]}…{text[^padding..]}" : text
|
||||||
|
};
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace BTCPayServer.Components.TruncateCenter
|
||||||
|
{
|
||||||
|
public class TruncateCenterViewModel
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
public string Truncated { get; set; }
|
||||||
|
public string Classes { get; set; }
|
||||||
|
public string Link { get; set; }
|
||||||
|
public int Padding { get; set; }
|
||||||
|
public bool Copy { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.InvoicingModels;
|
using BTCPayServer.Models.InvoicingModels;
|
||||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Payments.Bitcoin;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
|
@ -235,7 +236,9 @@ namespace BTCPayServer.Controllers
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
Link = link,
|
Link = link,
|
||||||
Id = txId,
|
Id = txId,
|
||||||
Destination = paymentData.GetDestination()
|
Destination = paymentData.GetDestination(),
|
||||||
|
PaymentProof = GetPaymentProof(paymentData),
|
||||||
|
PaymentType = paymentData.GetPaymentType()
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.Where(payment => payment != null)
|
.Where(payment => payment != null)
|
||||||
|
@ -247,6 +250,17 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? GetPaymentProof(CryptoPaymentData paymentData)
|
||||||
|
{
|
||||||
|
return paymentData switch
|
||||||
|
{
|
||||||
|
BitcoinLikePaymentData b => b.Outpoint.ToString(),
|
||||||
|
LightningPaymentData l => l.Preimage,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private string? GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
|
private string? GetTransactionLink(PaymentMethodId paymentMethodId, string txId)
|
||||||
{
|
{
|
||||||
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Validation;
|
using BTCPayServer.Validation;
|
||||||
|
@ -208,6 +209,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||||
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; }
|
public string Destination { get; set; }
|
||||||
|
public string PaymentProof { get; set; }
|
||||||
|
public PaymentType PaymentType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
@using BTCPayServer.Services
|
@using BTCPayServer.Services
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using BTCPayServer.Abstractions.TagHelpers
|
@using BTCPayServer.Abstractions.TagHelpers
|
||||||
|
@using BTCPayServer.Payments
|
||||||
@inject BTCPayServerEnvironment Env
|
@inject BTCPayServerEnvironment Env
|
||||||
@inject DisplayFormatter DisplayFormatter
|
@inject DisplayFormatter DisplayFormatter
|
||||||
@{
|
@{
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
}
|
}
|
||||||
<style>
|
<style>
|
||||||
#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:not(:last-child) tr:last-child > th,td { padding-bottom: 1rem; }
|
||||||
#posData td > table:last-child { margin-bottom: 0 !important; }
|
#posData td > table:last-child { margin-bottom: 0 !important; }
|
||||||
#posData table > tbody > tr:first-child > td > h4 { margin-top: 0 !important; }
|
#posData table > tbody > tr:first-child > td > h4 { margin-top: 0 !important; }
|
||||||
</style>
|
</style>
|
||||||
|
@ -114,39 +117,48 @@
|
||||||
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
||||||
<h2 class="h4 mb-3 d-print-none">Payment Details</h2>
|
<h2 class="h4 mb-3 d-print-none">Payment Details</h2>
|
||||||
<div class="table-responsive my-0">
|
<div class="table-responsive my-0">
|
||||||
<table class="table my-0">
|
<table class="table table-borderless my-0">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="fw-normal text-secondary">Destination</th>
|
<th class="fw-normal text-secondary">Date</th>
|
||||||
<th class="fw-normal text-secondary">Received</th>
|
<th class="fw-normal text-secondary">Payment</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>
|
||||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
@foreach (var payment in Model.Payments)
|
@foreach (var payment in Model.Payments)
|
||||||
{
|
{
|
||||||
<tr class="table-borderless table-light">
|
<tbody>
|
||||||
<td class="text-break">
|
<tr>
|
||||||
<code>@payment.Destination</code>
|
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td>
|
||||||
</td>
|
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td>
|
||||||
<td>@payment.ReceivedDate.ToString("g")</td>
|
<td class="text-end text-nowrap">@payment.PaidFormatted</td>
|
||||||
<td class="text-end">@payment.PaidFormatted</td>
|
<td class="text-end text-nowrap">@payment.RateFormatted</td>
|
||||||
<td class="text-end">@payment.RateFormatted</td>
|
|
||||||
<td class="text-end text-nowrap">@payment.Amount @payment.PaymentMethod</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="table-borderless table-light">
|
@if (!string.IsNullOrEmpty(payment.Destination))
|
||||||
<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>
|
<tr>
|
||||||
}
|
<th class="fw-normal text-nowrap text-secondary">
|
||||||
else
|
Destination
|
||||||
{
|
</th>
|
||||||
<span class="text-break">@payment.Id</span>
|
<td class="fw-normal" colspan="3">
|
||||||
}
|
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<th class="fw-normal text-nowrap text-secondary">
|
||||||
|
Payment Proof
|
||||||
|
</th>
|
||||||
|
<td class="fw-normal" colspan="3">
|
||||||
|
<vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -912,6 +912,54 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
||||||
padding-left: var(--btcpay-space-m);
|
padding-left: var(--btcpay-space-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Truncated text */
|
||||||
|
.truncate-center {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--btcpay-space-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate-center-id {
|
||||||
|
font-family: var(--btcpay-font-monospace);
|
||||||
|
font-size: .875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate-center-text {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate-center a,
|
||||||
|
.truncate-center button,
|
||||||
|
.truncate-center-truncated {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate-center button.btn .icon {
|
||||||
|
--btn-icon-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen {
|
||||||
|
.truncate-center-id {
|
||||||
|
background: var(--btcpay-neutral-100);
|
||||||
|
border: 1px solid var(--btcpay-neutral-200);
|
||||||
|
border-radius: var(--btcpay-border-radius-l);
|
||||||
|
padding: var(--btcpay-space-xs) var(--btcpay-space-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncate-center-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.truncate-center a,
|
||||||
|
.truncate-center button,
|
||||||
|
.truncate-center-truncated {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy */
|
/* Copy */
|
||||||
[data-clipboard],
|
[data-clipboard],
|
||||||
[data-clipboard] input[readonly] {
|
[data-clipboard] input[readonly] {
|
||||||
|
|
Loading…
Add table
Reference in a new issue