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:
d11n 2023-03-27 07:07:12 +02:00 committed by GitHub
parent c53d5272d6
commit de9ac9fd43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 166 additions and 31 deletions

View 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>

View 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);
}
}

View file

@ -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; }
}
}

View file

@ -19,6 +19,7 @@ using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Models.PaymentRequestViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
@ -235,7 +236,9 @@ namespace BTCPayServer.Controllers
PaymentMethod = paymentMethodId.ToPrettyString(),
Link = link,
Id = txId,
Destination = paymentData.GetDestination()
Destination = paymentData.GetDestination(),
PaymentProof = GetPaymentProof(paymentData),
PaymentType = paymentData.GetPaymentType()
};
})
.Where(payment => payment != null)
@ -247,6 +250,17 @@ namespace BTCPayServer.Controllers
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)
{
var network = _NetworkProvider.GetNetwork(paymentMethodId.CryptoCode);

View file

@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Validation;
@ -208,6 +209,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public string Link { get; set; }
public string Id { get; set; }
public string Destination { get; set; }
public string PaymentProof { get; set; }
public PaymentType PaymentType { get; set; }
}
}
}

View file

@ -5,6 +5,7 @@
@using BTCPayServer.Services
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Payments
@inject BTCPayServerEnvironment Env
@inject DisplayFormatter DisplayFormatter
@{
@ -27,6 +28,8 @@
}
<style>
#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 table > tbody > tr:first-child > td > h4 { margin-top: 0 !important; }
</style>
@ -114,38 +117,47 @@
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
<h2 class="h4 mb-3 d-print-none">Payment Details</h2>
<div class="table-responsive my-0">
<table class="table my-0">
<tr>
<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>
<table class="table table-borderless my-0">
<thead>
<tr>
<th class="fw-normal text-secondary">Date</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">Rate</th>
</tr>
</thead>
@foreach (var payment in Model.Payments)
{
<tr class="table-borderless table-light">
<td class="text-break">
<code>@payment.Destination</code>
</td>
<td>@payment.ReceivedDate.ToString("g")</td>
<td class="text-end">@payment.PaidFormatted</td>
<td class="text-end">@payment.RateFormatted</td>
<td class="text-end text-nowrap">@payment.Amount @payment.PaymentMethod</td>
</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>
<tr>
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td>
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td>
<td class="text-end text-nowrap">@payment.PaidFormatted</td>
<td class="text-end text-nowrap">@payment.RateFormatted</td>
</tr>
@if (!string.IsNullOrEmpty(payment.Destination))
{
<tr>
<th class="fw-normal text-nowrap text-secondary">
Destination
</th>
<td class="fw-normal" colspan="3">
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
</td>
</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>
</div>

View file

@ -912,6 +912,54 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
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 */
[data-clipboard],
[data-clipboard] input[readonly] {