Improve payment print styles (#1977)

* Improve payment print styles

Allows for export as invoice PDF to be used in accounting. Closes #1957.

* Change Transaction ID wording

* Minor payment request UI improvements

* Add amount paid, rate and colorize payment status

* Display rate at invoice level

* Inherit text color in print

* Show full date in print view

* Rearrange payment details

* Add received date for payments

* Fix amount calculation

* Fix validInvoice assignment
This commit is contained in:
Dennis Reimann
2020-10-23 10:37:28 +02:00
committed by GitHub
parent 7a711f0690
commit 20322c6ab8
7 changed files with 199 additions and 101 deletions

View File

@@ -234,9 +234,7 @@ namespace BTCPayServer.Controllers
} }
var statusesAllowedToDisplay = new List<InvoiceStatus>() { InvoiceStatus.New }; var statusesAllowedToDisplay = new List<InvoiceStatus>() { InvoiceStatus.New };
var validInvoice = result.Invoices.FirstOrDefault(invoice => var validInvoice = result.Invoices.FirstOrDefault(invoice => statusesAllowedToDisplay.Contains(invoice.Status));
Enum.TryParse<InvoiceStatus>(invoice.Status, true, out var status) &&
statusesAllowedToDisplay.Contains(status));
if (validInvoice != null) if (validInvoice != null)
{ {
@@ -297,7 +295,7 @@ namespace BTCPayServer.Controllers
} }
var invoices = result.Invoices.Where(requestInvoice => var invoices = result.Invoices.Where(requestInvoice =>
requestInvoice.Status.Equals(InvoiceState.ToString(InvoiceStatus.New), requestInvoice.StatusFormatted.Equals(InvoiceState.ToString(InvoiceStatus.New),
StringComparison.InvariantCulture) && !requestInvoice.Payments.Any()); StringComparison.InvariantCulture) && !requestInvoice.Payments.Any());
if (!invoices.Any()) if (!invoices.Any())

View File

@@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.Client.Models;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData; using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
@@ -139,7 +141,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public DateTime ExpiryDate { get; set; } public DateTime ExpiryDate { get; set; }
public decimal Amount { get; set; } public decimal Amount { get; set; }
public string AmountFormatted { get; set; } public string AmountFormatted { get; set; }
public string Status { get; set; } public InvoiceState State { get; set; }
public InvoiceStatus Status { get; set; }
public string StatusFormatted { get; set; }
public List<PaymentRequestInvoicePayment> Payments { get; set; } public List<PaymentRequestInvoicePayment> Payments { get; set; }
public string Currency { get; set; } public string Currency { get; set; }
@@ -149,6 +153,10 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
{ {
public string PaymentMethod { get; set; } public string PaymentMethod { get; set; }
public decimal Amount { get; set; } public decimal Amount { get; set; }
public string RateFormatted { get; set; }
public decimal Paid { get; set; }
public string PaidFormatted { 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; }
} }

View File

@@ -98,37 +98,52 @@ namespace BTCPayServer.PaymentRequest
AnyPendingInvoice = pendingInvoice != null, AnyPendingInvoice = pendingInvoice != null,
PendingInvoiceHasPayments = pendingInvoice != null && PendingInvoiceHasPayments = pendingInvoice != null &&
pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None, pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None,
Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice() Invoices = invoices.Select(entity =>
{ {
Id = entity.Id, var state = entity.GetInvoiceState();
Amount = entity.Price, return new ViewPaymentRequestViewModel.PaymentRequestInvoice
AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
Currency = entity.Currency,
ExpiryDate = entity.ExpirationTime.DateTime,
Status = entity.GetInvoiceState().ToString(),
Payments = entity
.GetPayments()
.Select(paymentEntity =>
{ {
var paymentData = paymentEntity.GetCryptoPaymentData(); Id = entity.Id,
var paymentMethodId = paymentEntity.GetPaymentMethodId(); Amount = entity.Price,
if (paymentData is null || paymentMethodId is null) AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency),
{ Currency = entity.Currency,
return null; ExpiryDate = entity.ExpirationTime.DateTime,
} State = state,
Status = state.Status,
StatusFormatted = state.ToString(),
Payments = entity
.GetPayments()
.Select(paymentEntity =>
{
var paymentData = paymentEntity.GetCryptoPaymentData();
var paymentMethodId = paymentEntity.GetPaymentMethodId();
if (paymentData is null || paymentMethodId is null)
{
return null;
}
string txId = paymentData.GetPaymentId(); string txId = paymentData.GetPaymentId();
string link = GetTransactionLink(paymentMethodId, txId); string link = GetTransactionLink(paymentMethodId, txId);
return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment() var paymentMethod = entity.GetPaymentMethod(paymentMethodId);
{ var amount = paymentData.GetValue();
Amount = paymentData.GetValue(), var rate = paymentMethod.Rate;
PaymentMethod = paymentMethodId.ToString(), var paid = (amount - paymentEntity.NetworkFee) * rate;
Link = link,
Id = txId return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment
}; {
}) Amount = amount,
.Where(payment => payment != null) Paid = paid,
.ToList() 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() }).ToList()
}; };
} }

View File

@@ -1,4 +1,6 @@
@model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel @using BTCPayServer.Services.Invoices
@using BTCPayServer.Client.Models
@model BTCPayServer.Models.PaymentRequestViewModels.ViewPaymentRequestViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@inject BTCPayServer.Services.BTCPayServerEnvironment env @inject BTCPayServer.Services.BTCPayServerEnvironment env
@@ -6,6 +8,31 @@
@{ @{
ViewData["Title"] = Model.Title; ViewData["Title"] = Model.Title;
Layout = null; Layout = null;
string StatusTextClass(InvoiceState state)
{
switch (state.Status)
{
case InvoiceStatus.Confirmed:
case InvoiceStatus.Complete:
case InvoiceStatus.Paid:
return "text-success";
case InvoiceStatus.Expired:
switch (state.ExceptionStatus)
{
case InvoiceExceptionStatus.PaidLate:
case InvoiceExceptionStatus.PaidPartial:
case InvoiceExceptionStatus.PaidOver:
return "text-warning";
default:
return "text-danger";
}
case InvoiceStatus.Invalid:
return "text-danger";
default:
return "text-warning";
}
}
} }
<!DOCTYPE html> <!DOCTYPE html>
@@ -50,7 +77,8 @@
<div class="col col-12 col-sm-6 col-lg-8 d-flex align-items-center"> <div class="col col-12 col-sm-6 col-lg-8 d-flex align-items-center">
<span class="text-muted text-nowrap">Last Updated</span> <span class="text-muted text-nowrap">Last Updated</span>
&nbsp; &nbsp;
<span class="text-nowrap" v-text="lastUpdated" v-cloak>@Model.LastUpdated.ToString("g")</span> <span class="text-nowrap d-print-none" v-text="lastUpdated" v-cloak>@Model.LastUpdated.ToString("g")</span>
<span class="text-nowrap d-none d-print-block" v-text="lastUpdatedDate">@Model.LastUpdated.ToString("g")</span>
<button type="button" class="btn btn-link d-none d-lg-inline-block d-print-none border-0 p-0 ml-4 only-for-js" v-on:click="window.print" v-cloak> <button type="button" class="btn btn-link d-none d-lg-inline-block d-print-none border-0 p-0 ml-4 only-for-js" v-on:click="window.print" v-cloak>
Print Print
</button> </button>
@@ -69,13 +97,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 pt-3 pb-2 col-md-4 py-md-0 col-lg-3 d-print-none"> <div class="col-12 pt-3 pb-2 col-md-4 py-md-0 col-lg-3">
<noscript> <noscript>
@if (Model.IsPending && !Model.Archived) @if (Model.IsPending && !Model.Archived)
{ {
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice) @if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
{ {
<form method="get" asp-action="PayPaymentRequest" asp-route-id="@Model.Id"> <form method="get" asp-action="PayPaymentRequest" asp-route-id="@Model.Id" class="d-print-none">
<div class="row"> <div class="row">
<div class="col col-12 col-sm-6 col-md-12"> <div class="col col-12 col-sm-6 col-md-12">
<div class="input-group"> <div class="input-group">
@@ -93,12 +121,12 @@
} }
else else
{ {
<a class="btn btn-primary d-inline-block w-100 text-nowrap @if (!(Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)) { @("btn-lg") }" asp-action="PayPaymentRequest" asp-route-id="@Model.Id"> <a class="btn btn-primary d-inline-block d-print-none w-100 text-nowrap @if (!(Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)) { @("btn-lg") }" asp-action="PayPaymentRequest" asp-route-id="@Model.Id">
Pay Invoice Pay Invoice
</a> </a>
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments) if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments)
{ {
<form method="get" asp-action="CancelUnpaidPendingInvoice" asp-route-id="@Model.Id" class="mt-2"> <form method="get" asp-action="CancelUnpaidPendingInvoice" asp-route-id="@Model.Id" class="mt-2 d-print-none">
<button class="btn btn-outline-secondary w-100 text-nowrap" type="submit">Cancel Invoice</button> <button class="btn btn-outline-secondary w-100 text-nowrap" type="submit">Cancel Invoice</button>
</form> </form>
} }
@@ -119,7 +147,7 @@
</noscript> </noscript>
<template v-if="srvModel.isPending && !srvModel.archived" class="d-print-none"> <template v-if="srvModel.isPending && !srvModel.archived" class="d-print-none">
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice"> <template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
<form v-on:submit="submitCustomAmountForm"> <form v-on:submit="submitCustomAmountForm" class="d-print-none">
<div class="row"> <div class="row">
<div class="col col-12 col-sm-6 col-md-12"> <div class="col col-12 col-sm-6 col-md-12">
<div class="input-group"> <div class="input-group">
@@ -141,13 +169,13 @@
</form> </form>
</template> </template>
<template v-else> <template v-else>
<button class="btn btn-primary w-100 d-flex align-items-center justify-content-center text-nowrap" :class="{ 'btn-lg': !(srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments)}" v-on:click="pay(null)" :disabled="loading"> <button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" :class="{ 'btn-lg': !(srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments)}" v-on:click="pay(null)" :disabled="loading">
<div v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status"> <div v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</div> </div>
<span>Pay Invoice</span> <span>Pay Invoice</span>
</button> </button>
<button class="btn btn-outline-secondary mt-2 w-100 d-flex align-items-center justify-content-center text-nowrap" v-if="srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments" v-on:click="cancelPayment()" :disabled="loading"> <button class="btn btn-outline-secondary mt-2 w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" v-if="srvModel.anyPendingInvoice && !srvModel.pendingInvoiceHasPayments" v-on:click="cancelPayment()" :disabled="loading">
<span v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status"> <span v-if="loading" class="spinner-grow spinner-grow-sm mr-2" role="status">
<span class="sr-only">Loading...</span> <span class="sr-only">Loading...</span>
</span> </span>
@@ -187,19 +215,19 @@
<div class="jumbotron h-100 m-0 p-sm-5"> <div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Payment Details</h2> <h2 class="h4 mb-3">Payment Details</h2>
<dl class="mb-0 mt-md-4"> <dl class="mb-0 mt-md-4">
<div class="d-flex flex-column mb-4"> <div class="d-flex d-print-inline-block flex-column mb-4 mr-5">
<dt class="h4 font-weight-normal text-nowrap text-primary order-2 order-sm-1 mb-0" v-text="srvModel.amountDueFormatted">@Model.AmountDueFormatted</dt> <dt class="h4 font-weight-normal text-nowrap text-primary text-print-default order-2 order-sm-1 mb-0" v-text="srvModel.amountDueFormatted">@Model.AmountDueFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Amount due</dd> <dd class="text-muted order-1 order-sm-2 mb-1">Amount due</dd>
</div> </div>
<div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px"> <div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px">
<div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected/Model.Amount)*100)%" v-bind:style="{ width: (srvModel.amountCollected/srvModel.amount*100) + '%' }"></div> <div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected/Model.Amount)*100)%" v-bind:style="{ width: (srvModel.amountCollected/srvModel.amount*100) + '%' }"></div>
</div> </div>
<div class="d-flex flex-column mb-4 d-sm-inline-flex mb-sm-0"> <div class="d-flex d-print-inline-block flex-column mb-4 mr-5 d-sm-inline-flex mb-sm-0">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountCollectedFormatted">@Model.AmountCollectedFormatted</dt> <dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountCollectedFormatted">@Model.AmountCollectedFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Amount paid</dd> <dd class="text-muted order-1 order-sm-2 mb-1">Amount paid</dd>
</div> </div>
<div class="d-flex flex-column mb-0 d-sm-inline-flex float-sm-right"> <div class="d-flex d-print-inline-block flex-column mb-0 d-sm-inline-flex float-sm-right">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountFormatted">@Model.AmountFormatted</dt> <dt class="h4 text-sm-right font-weight-normal text-nowrap order-2 order-sm-1 mb-0" v-text="srvModel.amountFormatted">@Model.AmountFormatted</dt>
<dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Total requested</dd> <dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Total requested</dd>
</div> </div>
</dl> </dl>
@@ -222,8 +250,9 @@
<thead> <thead>
<tr class="table-borderless"> <tr class="table-borderless">
<th class="font-weight-normal text-secondary" scope="col">Invoice Id</th> <th class="font-weight-normal text-secondary" scope="col">Invoice Id</th>
<th class="font-weight-normal text-secondary">Price</th> <th class="font-weight-normal text-secondary w-175px">Expiry</th>
<th class="font-weight-normal text-secondary">Expiry</th> <th class="font-weight-normal text-secondary text-right w-100px">Amount</th>
<th class="font-weight-normal text-secondary text-right w-125px"></th>
<th class="font-weight-normal text-secondary text-right">Status</th> <th class="font-weight-normal text-secondary text-right">Status</th>
</tr> </tr>
</thead> </thead>
@@ -232,32 +261,37 @@
{ {
<tr> <tr>
<td>@invoice.Id</td> <td>@invoice.Id</td>
<td>@invoice.Amount @invoice.Currency</td>
<td>@invoice.ExpiryDate.ToString("g")</td> <td>@invoice.ExpiryDate.ToString("g")</td>
<td class="text-right">@invoice.Status</td> <td class="text-right">@invoice.AmountFormatted</td>
<td class="text-right"></td>
<td class="text-right text-print-default @StatusTextClass(invoice.State)">@invoice.StatusFormatted</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 colspan="2" class="pl-3 font-weight-normal text-secondary">TX Id</th> <th class="pl-3 font-weight-normal text-secondary">Transaction Id</th>
<th class="font-weight-normal text-secondary">Payment Method</th> <th class="font-weight-normal text-secondary">Received</th>
<th class="font-weight-normal text-secondary text-right">Amount</th> <th class="font-weight-normal text-secondary text-right">Paid</th>
<th class="font-weight-normal text-secondary text-right">Rate</th>
<th class="font-weight-normal text-secondary text-right">Payment</th>
</tr> </tr>
@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 colspan="2" class="pl-3 text-break"> <td class="pl-3 text-break">
@if (!string.IsNullOrEmpty(payment.Link)) @if (!string.IsNullOrEmpty(payment.Link))
{ {
<a href="@payment.Link" target="_blank">@payment.Id</a> <a href="@payment.Link" class="text-print-default" target="_blank">@payment.Id</a>
} }
else else
{ {
<span>@payment.Id</span> <span>@payment.Id</span>
} }
</td> </td>
<td>@payment.PaymentMethod</td> <td>@payment.ReceivedDate.ToString("g")</td>
<td class="text-right">@payment.Amount</td> <td class="text-right">@payment.PaidFormatted</td>
<td class="text-right">@payment.RateFormatted</td>
<td class="text-right text-nowrap">@payment.Amount @payment.PaymentMethod</td>
</tr> </tr>
} }
} }
@@ -270,37 +304,45 @@
<template v-if="!srvModel.invoices || srvModel.invoices.length == 0"> <template v-if="!srvModel.invoices || srvModel.invoices.length == 0">
<p class="text-muted">No payments made yet.</p> <p class="text-muted">No payments made yet.</p>
</template> </template>
<template v-else v-for="invoice of srvModel.invoices" :key="invoice.id"> <template v-else>
<table class="table my-0"> <table class="table my-0">
<thead> <thead>
<tr class="table-borderless"> <tr class="table-borderless">
<th class="font-weight-normal text-secondary" scope="col">Invoice Id</th> <th class="font-weight-normal text-secondary" scope="col">Invoice Id</th>
<th class="font-weight-normal text-secondary">Price</th> <th class="font-weight-normal text-secondary w-175px">Expiry</th>
<th class="font-weight-normal text-secondary">Expiry</th> <th class="font-weight-normal text-secondary text-right w-100px">Amount</th>
<th class="font-weight-normal text-secondary text-right w-125px"></th>
<th class="font-weight-normal text-secondary text-right">Status</th> <th class="font-weight-normal text-secondary text-right">Status</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <template v-for="invoice of srvModel.invoices" :key="invoice.id">
<td>{{invoice.id}}</td> <tr>
<td>{{invoice.amountFormatted}}</td> <td>{{invoice.id}}</td>
<td>{{moment(invoice.expiryDate).format('L HH:mm')}}</td> <td v-text="formatDate(invoice.expiryDate)"></td>
<td class="text-right">{{invoice.status}}</td> <td class="text-right">{{invoice.amountFormatted}}</td>
</tr> <td class="text-right"></td>
<template v-if="invoice.payments && invoice.payments.length > 0"> <td class="text-right text-print-default" :class="statusTextClass(invoice.statusFormatted)">{{invoice.statusFormatted}}</td>
<tr class="table-borderless table-light">
<th colspan="2" class="pl-3 font-weight-normal text-secondary">TX Id</th>
<th class="font-weight-normal text-secondary">Payment Method</th>
<th class="font-weight-normal text-secondary text-right">Amount</th>
</tr>
<tr v-for="payment of invoice.payments" class="table-borderless table-light">
<td colspan="2" class="pl-3 text-break">
<a v-if="payment.link" :href="payment.link" target="_blank">{{payment.id}}</a>
<span v-else>{{payment.id}}</span>
</td>
<td>{{formatPaymentMethod(payment.paymentMethod)}}</td>
<td class="text-right">{{payment.amount.noExponents()}}</td>
</tr> </tr>
<template v-if="invoice.payments && invoice.payments.length > 0">
<tr class="table-borderless table-light">
<th class="pl-3 font-weight-normal text-secondary">Transaction Id</th>
<th class="font-weight-normal text-secondary">Received</th>
<th class="font-weight-normal text-secondary text-right">Paid</th>
<th class="font-weight-normal text-secondary text-right">Rate</th>
<th class="font-weight-normal text-secondary text-right">Payment</th>
</tr>
<tr v-for="payment of invoice.payments" class="table-borderless table-light">
<td class="pl-3 text-break">
<a v-if="payment.link" :href="payment.link" class="text-print-default" target="_blank">{{payment.id}}</a>
<span v-else>{{payment.id}}</span>
</td>
<td v-text="formatDate(payment.receivedDate)"></td>
<td class="text-right">{{payment.paidFormatted}}</td>
<td class="text-right">{{payment.rateFormatted}}</td>
<td class="text-right text-nowrap">{{payment.amount.noExponents()}} {{payment.paymentMethod}}</td>
</tr>
</template>
</template> </template>
</tbody> </tbody>
</table> </table>

View File

@@ -50,7 +50,7 @@
<div class="h-100 d-flex flex-column"> <div class="h-100 d-flex flex-column">
@if (Model.IsPending) @if (Model.IsPending)
{ {
<nav id="mainNav" class="navbar sticky-top py-3 py-lg-4 d-print-block"> <nav id="mainNav" class="navbar sticky-top py-3 py-lg-4 d-print-none">
<div class="container"> <div class="container">
<form asp-action="ClaimPullPayment" asp-route-pullPaymentId="@Model.Id" class="w-100"> <form asp-action="ClaimPullPayment" asp-route-pullPaymentId="@Model.Id" class="w-100">
<div class="row align-items-center" style="width:calc(100% + 30px)"> <div class="row align-items-center" style="width:calc(100% + 30px)">
@@ -114,19 +114,19 @@
<div class="jumbotron h-100 m-0 p-sm-5"> <div class="jumbotron h-100 m-0 p-sm-5">
<h2 class="h4 mb-3">Payment Details</h2> <h2 class="h4 mb-3">Payment Details</h2>
<dl class="mb-0 mt-md-4"> <dl class="mb-0 mt-md-4">
<div class="d-flex flex-column mb-4"> <div class="d-flex d-print-inline-block flex-column mb-4 mr-5">
<dt class="h4 font-weight-normal text-nowrap text-primary order-2 order-sm-1 mb-0">@Model.AmountDueFormatted</dt> <dt class="h4 font-weight-normal text-nowrap text-primary text-print-default order-2 order-sm-1 mb-0">@Model.AmountDueFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Available claim</dd> <dd class="text-muted order-1 order-sm-2 mb-1">Available claim</dd>
</div> </div>
<div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px"> <div class="progress bg-light d-none d-sm-flex mb-sm-4 d-print-none" style="height:5px">
<div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected / Model.Amount) * 100)%"></div> <div class="progress-bar bg-primary" role="progressbar" style="width:@((Model.AmountCollected / Model.Amount) * 100)%"></div>
</div> </div>
<div class="d-flex flex-column mb-4 d-sm-inline-flex mb-sm-0"> <div class="d-flex d-print-inline-block flex-column mb-4 mr-5 d-sm-inline-flex mb-sm-0">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountCollectedFormatted</dt> <dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountCollectedFormatted</dt>
<dd class="text-muted order-1 order-sm-2 mb-1">Already claimed</dd> <dd class="text-muted order-1 order-sm-2 mb-1">Already claimed</dd>
</div> </div>
<div class="d-flex flex-column mb-0 d-sm-inline-flex float-sm-right"> <div class="d-flex d-print-inline-block flex-column mb-0 d-sm-inline-flex float-sm-right">
<dt class="h4 font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountFormatted</dt> <dt class="h4 text-sm-right font-weight-normal text-nowrap order-2 order-sm-1 mb-0">@Model.AmountFormatted</dt>
<dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Claim limit</dd> <dd class="text-muted text-sm-right order-1 order-sm-2 mb-1">Claim limit</dd>
</div> </div>
</dl> </dl>
@@ -159,11 +159,11 @@
<td class="text-right text-nowrap"> <td class="text-right text-nowrap">
@if (!string.IsNullOrEmpty(invoice.Link)) @if (!string.IsNullOrEmpty(invoice.Link))
{ {
<a class="transaction-link @StatusTextClass(invoice.Status)" href="@invoice.Link">@invoice.Status</a> <a class="transaction-link text-print-default @StatusTextClass(invoice.Status)" href="@invoice.Link">@invoice.Status</a>
} }
else else
{ {
<span class="@StatusTextClass(invoice.Status)">@invoice.Status</span> <span class="text-print-default @StatusTextClass(invoice.Status)">@invoice.Status</span>
} }
</td> </td>
</tr> </tr>

View File

@@ -249,6 +249,12 @@ pre {
margin-top: .5rem; margin-top: .5rem;
} }
.w-100px { width: 100px; }
.w-125px { width: 125px; }
.w-150px { width: 150px; }
.w-175px { width: 175px; }
.w-200px { width: 200px; }
/* Chrome, Safari, Edge, Opera */ /* Chrome, Safari, Edge, Opera */
input[type=number].hide-number-spin::-webkit-outer-spin-button, input[type=number].hide-number-spin::-webkit-outer-spin-button,
input[type=number].hide-number-spin::-webkit-inner-spin-button { input[type=number].hide-number-spin::-webkit-inner-spin-button {
@@ -268,8 +274,8 @@ html[data-devenv]:before {
z-index: 1000; z-index: 1000;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: var(--btcpay-bg-dark); background: var(--btcpay-color-secondary-backdrop);
color: var(--btcpay-color-dark-text); color: var(--btcpay-color-secondary-text);
opacity: .7; opacity: .7;
padding: 4px 5px 3px 7px; padding: 4px 5px 3px 7px;
font-size: 10px; font-size: 10px;
@@ -285,9 +291,19 @@ html[data-devenv]:before {
/* Print */ /* Print */
@media print { @media print {
.table td,
.table th {
background: transparent;
}
.jumbotron { .jumbotron {
padding: 1rem 0 !important; padding: 1rem 0 !important;
} }
.text-print-default {
color: inherit !important;
}
a {
text-decoration: none !important;
}
} }
/* Richtext editor */ /* Richtext editor */

View File

@@ -43,6 +43,9 @@ addLoadEvent(function (ev) {
lastUpdated: function () { lastUpdated: function () {
return this.srvModel.lastUpdated && moment(this.srvModel.lastUpdated).calendar(); return this.srvModel.lastUpdated && moment(this.srvModel.lastUpdated).calendar();
}, },
lastUpdatedDate: function () {
return this.srvModel.lastUpdated && moment(this.srvModel.lastUpdated).format('MMMM Do YYYY, h:mm:ss a');
},
active: function () { active: function () {
return !this.ended; return !this.ended;
} }
@@ -90,26 +93,42 @@ addLoadEvent(function (ev) {
self.timeoutState = setTimeout(function () { self.timeoutState = setTimeout(function () {
self.setLoading(false); self.setLoading(false);
}, 5000); }, 5000);
eventAggregator.$emit("cancel-invoice", amount); eventAggregator.$emit("cancel-invoice", amount);
}, },
formatPaymentMethod: function (str) { formatDate: function (date) {
return moment(date).format('L h:mm A')
if (str.endsWith("LightningLike")) {
return str.replace("LightningLike", "Lightning")
}
return str;
}, },
submitCustomAmountForm : function(e){ submitCustomAmountForm: function(e) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
} }
if(this.srvModel.allowCustomPaymentAmounts && parseFloat(this.customAmount) < this.srvModel.amountDue){ if (this.srvModel.allowCustomPaymentAmounts && parseFloat(this.customAmount) < this.srvModel.amountDue){
this.pay(parseFloat(this.customAmount)); this.pay(parseFloat(this.customAmount));
}else{ } else {
this.pay(); this.pay();
} }
},
statusTextClass: function (state) {
var [, status,, exceptionStatus] = state.match(/(\w*)\s?(\((\w*)\))?/) || [];
switch (status) {
case "confirmed":
case "complete":
case "paid":
return "text-success";
case "expired":
switch (exceptionStatus) {
case "paidLate":
case "paidPartial":
case "paidOver":
return "text-warning";
default:
return "text-danger";
}
case "invalid":
return "text-danger";
default:
return "text-warning";
}
} }
}, },
mounted: function () { mounted: function () {