mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Payment Request: Add processing status for on-chain payments (#5309)
Closes #5297.
This commit is contained in:
parent
f034e2cd65
commit
17d1832dad
@ -7,7 +7,7 @@ namespace BTCPayServer.Client.Models
|
||||
public class PaymentRequestData : PaymentRequestBaseData
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public PaymentRequestData.PaymentRequestStatus Status { get; set; }
|
||||
public PaymentRequestStatus Status { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
public string Id { get; set; }
|
||||
@ -16,7 +16,8 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
Pending = 0,
|
||||
Completed = 1,
|
||||
Expired = 2
|
||||
Expired = 2,
|
||||
Processing = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1606,7 +1606,7 @@ namespace BTCPayServer.Tests
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
await user.MakeAdmin();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
var viewOnly = await user.CreateClient(Policies.CanViewPaymentRequests);
|
||||
@ -1688,11 +1688,18 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
|
||||
});
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
|
||||
if (!partialPayment)
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_PAID, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
|
||||
if (!partialPayment)
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Processing, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
Assert.Equal(Invoice.STATUS_CONFIRMED, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
|
||||
if (!partialPayment)
|
||||
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
|
||||
});
|
||||
}
|
||||
await Pay(invoiceId);
|
||||
|
||||
|
@ -31,7 +31,6 @@ namespace BTCPayServer.Tests
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var user2 = tester.NewAccount();
|
||||
|
||||
await user2.GrantAccessAsync();
|
||||
|
||||
var paymentRequestController = user.GetController<UIPaymentRequestController>();
|
||||
@ -162,7 +161,7 @@ namespace BTCPayServer.Tests
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var paymentRequestController = user.GetController<UIPaymentRequestController>();
|
||||
@ -170,7 +169,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<NotFoundResult>(await
|
||||
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
|
||||
|
||||
var request = new UpdatePaymentRequestViewModel()
|
||||
var request = new UpdatePaymentRequestViewModel
|
||||
{
|
||||
Title = "original juice",
|
||||
Currency = "BTC",
|
||||
|
@ -1159,13 +1159,13 @@ namespace BTCPayServer.Tests
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.EnableCheckout(CheckoutType.V1);
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
|
||||
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys(".01");
|
||||
|
||||
var currencyInput = s.Driver.FindElement(By.Id("Currency"));
|
||||
Assert.Equal("USD", currencyInput.GetAttribute("value"));
|
||||
@ -1208,9 +1208,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// test invoice creation, click with JS, because the button is inside a sticky header
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
||||
// checkout v1
|
||||
s.Driver.WaitForElement(By.CssSelector("invoice"));
|
||||
Assert.Contains("Awaiting Payment", s.Driver.PageSource);
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
// amount and currency should not be editable, because invoice exists
|
||||
s.GoToUrl(editUrl);
|
||||
@ -1231,6 +1229,36 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.WaitForElement(By.Id($"ToggleArchival-{payReqId}")).Click();
|
||||
Assert.Contains("The payment request has been unarchived", s.FindAlertMessage().Text);
|
||||
Assert.Contains("Pay123", s.Driver.PageSource);
|
||||
|
||||
// payment
|
||||
s.GoToUrl(viewUrl);
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
||||
|
||||
// Pay full amount
|
||||
s.PayInvoice();
|
||||
|
||||
// Processing
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
var processingSection = s.Driver.WaitForElement(By.Id("processing"));
|
||||
Assert.True(processingSection.Displayed);
|
||||
Assert.Contains("Payment Received", processingSection.Text);
|
||||
Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
|
||||
});
|
||||
|
||||
s.GoToUrl(viewUrl);
|
||||
Assert.Equal("Processing", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||
s.Driver.Navigate().Back();
|
||||
|
||||
// Mine
|
||||
s.MineBlockOnInvoiceCheckout();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
});
|
||||
s.GoToUrl(viewUrl);
|
||||
Assert.Equal("Settled", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
@ -333,7 +333,7 @@ namespace BTCPayServer.Controllers
|
||||
try
|
||||
{
|
||||
var store = await _storeRepository.FindStore(result.StoreId);
|
||||
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null);
|
||||
var prData = await _PaymentRequestRepository.FindPaymentRequest(result.Id, null, cancellationToken);
|
||||
var newInvoice = await _InvoiceController.CreatePaymentRequestInvoice(prData, amount, result.AmountDue, store, Request, cancellationToken);
|
||||
if (redirectToInvoice)
|
||||
{
|
||||
|
@ -119,6 +119,9 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
Status = "Pending";
|
||||
IsPending = true;
|
||||
break;
|
||||
case Client.Models.PaymentRequestData.PaymentRequestStatus.Processing:
|
||||
Status = "Processing";
|
||||
break;
|
||||
case Client.Models.PaymentRequestData.PaymentRequestStatus.Completed:
|
||||
Status = "Settled";
|
||||
break;
|
||||
|
@ -23,6 +23,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
private readonly UIPaymentRequestController _PaymentRequestController;
|
||||
public const string InvoiceCreated = "InvoiceCreated";
|
||||
public const string InvoiceConfirmed = "InvoiceConfirmed";
|
||||
public const string PaymentReceived = "PaymentReceived";
|
||||
public const string InfoUpdated = "InfoUpdated";
|
||||
public const string InvoiceError = "InvoiceError";
|
||||
@ -128,9 +129,13 @@ namespace BTCPayServer.PaymentRequest
|
||||
private async Task CheckingPendingPayments(CancellationToken cancellationToken)
|
||||
{
|
||||
Logs.PayServer.LogInformation("Starting payment request expiration watcher");
|
||||
var items = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
|
||||
var items = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery
|
||||
{
|
||||
Status = new[] { Client.Models.PaymentRequestData.PaymentRequestStatus.Pending }
|
||||
Status = new[]
|
||||
{
|
||||
PaymentRequestData.PaymentRequestStatus.Pending,
|
||||
PaymentRequestData.PaymentRequestStatus.Processing
|
||||
}
|
||||
}, cancellationToken);
|
||||
Logs.PayServer.LogInformation($"{items.Length} pending payment requests being checked since last run");
|
||||
await Task.WhenAll(items.Select(i => _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(i))
|
||||
@ -157,7 +162,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
foreach (var paymentId in PaymentRequestRepository.GetPaymentIdsFromInternalTags(invoiceEvent.Invoice))
|
||||
{
|
||||
if (invoiceEvent.Name == InvoiceEvent.ReceivedPayment || invoiceEvent.Name == InvoiceEvent.MarkedCompleted || invoiceEvent.Name == InvoiceEvent.MarkedInvalid)
|
||||
if (invoiceEvent.Name is InvoiceEvent.ReceivedPayment or InvoiceEvent.MarkedCompleted or InvoiceEvent.MarkedInvalid)
|
||||
{
|
||||
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(paymentId);
|
||||
var data = invoiceEvent.Payment?.GetCryptoPaymentData();
|
||||
@ -168,10 +173,19 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
data.GetValue(),
|
||||
invoiceEvent.Payment.Currency,
|
||||
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType?.ToString()
|
||||
invoiceEvent.Payment.GetPaymentMethodId()?.PaymentType.ToString()
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (invoiceEvent.Name is InvoiceEvent.Completed or InvoiceEvent.Confirmed)
|
||||
{
|
||||
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(paymentId);
|
||||
await _HubContext.Clients.Group(paymentId).SendCoreAsync(PaymentRequestHub.InvoiceConfirmed,
|
||||
new object[]
|
||||
{
|
||||
invoiceEvent.InvoiceId
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
await InfoUpdated(paymentId);
|
||||
}
|
||||
@ -181,10 +195,11 @@ namespace BTCPayServer.PaymentRequest
|
||||
await _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(updated.PaymentRequestId);
|
||||
await InfoUpdated(updated.PaymentRequestId);
|
||||
|
||||
var isPending = updated.Data.Status is
|
||||
PaymentRequestData.PaymentRequestStatus.Pending or
|
||||
PaymentRequestData.PaymentRequestStatus.Processing;
|
||||
var expiry = updated.Data.GetBlob().ExpiryDate;
|
||||
if (updated.Data.Status ==
|
||||
PaymentRequestData.PaymentRequestStatus.Pending &&
|
||||
expiry.HasValue)
|
||||
if (isPending && expiry.HasValue)
|
||||
{
|
||||
QueueExpiryTask(
|
||||
updated.PaymentRequestId,
|
||||
|
@ -10,7 +10,6 @@ using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.PaymentRequests;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
||||
|
||||
namespace BTCPayServer.PaymentRequest
|
||||
@ -27,7 +26,6 @@ namespace BTCPayServer.PaymentRequest
|
||||
PaymentRequestRepository paymentRequestRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
AppService appService,
|
||||
DisplayFormatter displayFormatter,
|
||||
CurrencyNameTable currencies)
|
||||
{
|
||||
@ -62,10 +60,19 @@ namespace BTCPayServer.PaymentRequest
|
||||
{
|
||||
var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(pr.Id);
|
||||
var contributions = _invoiceRepository.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||
var allSettled = contributions.All(i => i.Value.States.All(s => s.IsSettled()));
|
||||
var isPaid = contributions.TotalCurrency >= blob.Amount;
|
||||
|
||||
currentStatus = contributions.TotalCurrency >= blob.Amount
|
||||
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
||||
: Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
||||
if (isPaid)
|
||||
{
|
||||
currentStatus = allSettled
|
||||
? Client.Models.PaymentRequestData.PaymentRequestStatus.Completed
|
||||
: Client.Models.PaymentRequestData.PaymentRequestStatus.Processing;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStatus = Client.Models.PaymentRequestData.PaymentRequestStatus.Pending;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStatus != pr.Status)
|
||||
@ -86,12 +93,11 @@ namespace BTCPayServer.PaymentRequest
|
||||
var blob = pr.GetBlob();
|
||||
|
||||
var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(id);
|
||||
|
||||
var paymentStats = _invoiceRepository.GetContributionsByPaymentMethodId(blob.Currency, invoices, true);
|
||||
var amountDue = blob.Amount - paymentStats.TotalCurrency;
|
||||
var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime)
|
||||
.FirstOrDefault(entity => entity.Status == InvoiceStatusLegacy.New);
|
||||
|
||||
|
||||
return new ViewPaymentRequestViewModel(pr)
|
||||
{
|
||||
Archived = pr.Archived,
|
||||
|
@ -935,6 +935,14 @@ namespace BTCPayServer.Services.Invoices
|
||||
Status == InvoiceStatusLegacy.Invalid;
|
||||
}
|
||||
|
||||
public bool IsSettled()
|
||||
{
|
||||
return Status == InvoiceStatusLegacy.Confirmed ||
|
||||
Status == InvoiceStatusLegacy.Complete ||
|
||||
(Status == InvoiceStatusLegacy.Expired &&
|
||||
ExceptionStatus is InvoiceExceptionStatus.PaidLate or InvoiceExceptionStatus.PaidOver);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Status, ExceptionStatus);
|
||||
@ -970,7 +978,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return Status.ToModernStatus().ToString() + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})");
|
||||
return Status.ToModernStatus() + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -786,9 +786,12 @@ namespace BTCPayServer.Services.Invoices
|
||||
.Where(p => p.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(p =>
|
||||
{
|
||||
var contribution = new InvoiceStatistics.Contribution();
|
||||
contribution.PaymentMethodId = new PaymentMethodId(p.Currency, PaymentTypes.BTCLike);
|
||||
contribution.CurrencyValue = p.Price;
|
||||
var contribution = new InvoiceStatistics.Contribution
|
||||
{
|
||||
PaymentMethodId = new PaymentMethodId(p.Currency, PaymentTypes.BTCLike),
|
||||
CurrencyValue = p.Price,
|
||||
States = new [] { p.GetInvoiceState() }
|
||||
};
|
||||
contribution.Value = contribution.CurrencyValue;
|
||||
|
||||
// For hardcap, we count newly created invoices as part of the contributions
|
||||
@ -815,18 +818,22 @@ namespace BTCPayServer.Services.Invoices
|
||||
return payments
|
||||
.Select(pay =>
|
||||
{
|
||||
var paymentMethodContribution = new InvoiceStatistics.Contribution();
|
||||
paymentMethodContribution.PaymentMethodId = pay.GetPaymentMethodId();
|
||||
paymentMethodContribution.CurrencyValue = pay.InvoicePaidAmount.Net;
|
||||
paymentMethodContribution.Value = pay.PaidAmount.Net;
|
||||
var paymentMethodContribution = new InvoiceStatistics.Contribution
|
||||
{
|
||||
PaymentMethodId = pay.GetPaymentMethodId(),
|
||||
CurrencyValue = pay.InvoicePaidAmount.Net,
|
||||
Value = pay.PaidAmount.Net,
|
||||
States = new [] { pay.InvoiceEntity.GetInvoiceState() }
|
||||
};
|
||||
return paymentMethodContribution;
|
||||
})
|
||||
.ToArray();
|
||||
})
|
||||
.GroupBy(p => p.PaymentMethodId)
|
||||
.ToDictionary(p => p.Key, p => new InvoiceStatistics.Contribution()
|
||||
.ToDictionary(p => p.Key, p => new InvoiceStatistics.Contribution
|
||||
{
|
||||
PaymentMethodId = p.Key,
|
||||
States = p.SelectMany(v => v.States),
|
||||
Value = p.Select(v => v.Value).Sum(),
|
||||
CurrencyValue = p.Select(v => v.CurrencyValue).Sum()
|
||||
});
|
||||
@ -913,6 +920,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
public class Contribution
|
||||
{
|
||||
public PaymentMethodId PaymentMethodId { get; set; }
|
||||
public IEnumerable<InvoiceState> States { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public decimal CurrencyValue { get; set; }
|
||||
}
|
||||
|
@ -10,25 +10,21 @@
|
||||
Layout = null;
|
||||
string StatusClass(InvoiceState state)
|
||||
{
|
||||
switch (state.Status.ToModernStatus())
|
||||
var status = state.Status.ToModernStatus();
|
||||
switch (status)
|
||||
{
|
||||
case InvoiceStatus.Settled:
|
||||
case InvoiceStatus.Processing:
|
||||
return "success";
|
||||
case InvoiceStatus.Expired:
|
||||
switch (state.ExceptionStatus)
|
||||
{
|
||||
case InvoiceExceptionStatus.PaidLate:
|
||||
case InvoiceExceptionStatus.PaidPartial:
|
||||
case InvoiceExceptionStatus.PaidOver:
|
||||
return "warning";
|
||||
return "unusual";
|
||||
default:
|
||||
return "danger";
|
||||
return "expired";
|
||||
}
|
||||
case InvoiceStatus.Invalid:
|
||||
return "danger";
|
||||
default:
|
||||
return "warning";
|
||||
return status.ToString().ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,7 +127,7 @@
|
||||
else
|
||||
{
|
||||
<div class="h2 text-md-end">
|
||||
<span class="badge @if (Model.Status == "Settled") { @("bg-primary") } else if (Model.Status == "Expired") { @("bg-danger") } else { @("bg-info") }" data-test="status">
|
||||
<span class="badge badge-@Model.Status.ToLowerInvariant()" data-test="status" style="font-size:.75em">
|
||||
@Model.Status
|
||||
@if (Model.Archived)
|
||||
{
|
||||
@ -186,7 +182,7 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="h2 text-md-end">
|
||||
<span class="badge" :class="{ 'bg-primary': srvModel.status === 'Settled', 'bg-danger': srvModel.status === 'Expired', 'bg-info': (srvModel.status !== 'Settled' && srvModel.status !== 'Expired') }" data-test="status">
|
||||
<span class="badge" :class="`badge-${srvModel.status.toLowerCase()}`" data-test="status" style="font-size:.75em">
|
||||
{{srvModel.status}}
|
||||
<span v-if="srvModel.archived">(archived)</span>
|
||||
</span>
|
||||
@ -262,18 +258,18 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<td>@invoice.Id</td>
|
||||
<td>@invoice.ExpiryDate.ToString("g")</td>
|
||||
<td class="text-end">@invoice.AmountFormatted</td>
|
||||
<td class="text-end"></td>
|
||||
<td class="text-end text-print-default">
|
||||
<span class="badge bg-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
||||
<span class="badge badge-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
||||
</td>
|
||||
</tr>
|
||||
@if (invoice.Payments != null && invoice.Payments.Any())
|
||||
{
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<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>
|
||||
@ -282,14 +278,14 @@
|
||||
</tr>
|
||||
@foreach (var payment in invoice.Payments)
|
||||
{
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<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">
|
||||
<tr class="table-borderless">
|
||||
<td class="fw-normal" colspan="5">
|
||||
<span class="text-secondary">Transaction Id:</span>
|
||||
@if (!string.IsNullOrEmpty(payment.Link))
|
||||
@ -326,17 +322,17 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<td>{{invoice.id}}</td>
|
||||
<td v-text="formatDate(invoice.expiryDate)"></td>
|
||||
<td class="text-end">{{invoice.amountFormatted}}</td>
|
||||
<td class="text-end"></td>
|
||||
<td class="text-end text-print-default">
|
||||
<span class="badge" :class="`bg-${statusClass(invoice.stateFormatted)}`">{{invoice.stateFormatted}}</span>
|
||||
<span class="badge" :class="`badge-${statusClass(invoice.stateFormatted)}`">{{invoice.stateFormatted}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="invoice.payments && invoice.payments.length > 0">
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<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>
|
||||
@ -344,14 +340,14 @@
|
||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
||||
</tr>
|
||||
<template v-for="payment of invoice.payments">
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<td class="text-break"><code>{{payment.destination}}</code></td>
|
||||
<td v-text="formatDate(payment.receivedDate)"></td>
|
||||
<td class="text-end">{{payment.paidFormatted}}</td>
|
||||
<td class="text-end">{{payment.rateFormatted}}</td>
|
||||
<td class="text-end text-nowrap">{{payment.amount.noExponents()}} {{payment.paymentMethod}}</td>
|
||||
</tr>
|
||||
<tr class="table-borderless table-light">
|
||||
<tr class="table-borderless">
|
||||
<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>
|
||||
|
@ -102,30 +102,30 @@ a.unobtrusive-link {
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge-pending,
|
||||
.badge-new {
|
||||
.badge-new,
|
||||
.badge-pending {
|
||||
background: #d4edda;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge-expired {
|
||||
background: #eee;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge-invalid {
|
||||
background: #c94a47;
|
||||
color: #fff;
|
||||
background: var(--btcpay-danger);
|
||||
color: var(--btcpay-danger-text);
|
||||
}
|
||||
.badge-unusual {
|
||||
background: var(--btcpay-warning);
|
||||
color: var(--btcpay-warning-text);
|
||||
}
|
||||
|
||||
.badge-processing {
|
||||
background: #f1c332;
|
||||
color: #000;
|
||||
background: var(--btcpay-info);
|
||||
color: var(--btcpay-info-text);
|
||||
}
|
||||
|
||||
.badge-settled {
|
||||
background: #329f80;
|
||||
color: #fff;
|
||||
background: var(--btcpay-success);
|
||||
color: var(--btcpay-success-text);
|
||||
}
|
||||
|
||||
/* Info icons in main headline */
|
||||
|
@ -95,24 +95,19 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
}
|
||||
},
|
||||
statusClass: function (state) {
|
||||
var [, status,, exceptionStatus] = state.match(/(\w*)\s?(\((\w*)\))?/) || [];
|
||||
const [, status,, exceptionStatus] = state.match(/(\w*)\s?(\((\w*)\))?/) || [];
|
||||
switch (status) {
|
||||
case "Settled":
|
||||
case "Processing":
|
||||
return "success";
|
||||
case "Expired":
|
||||
switch (exceptionStatus) {
|
||||
case "paidLate":
|
||||
case "paidPartial":
|
||||
case "paidOver":
|
||||
return "warning";
|
||||
return "unusual";
|
||||
default:
|
||||
return "danger";
|
||||
return "expired";
|
||||
}
|
||||
case "Invalid":
|
||||
return "danger";
|
||||
default:
|
||||
return "warning";
|
||||
return status.toLowerCase();
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -164,7 +159,6 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
Vue.toasted.success(title, Object.assign({}, toastOptions), { icon });
|
||||
});
|
||||
eventAggregator.$on("info-updated", function (model) {
|
||||
console.warn("UPDATED", self.srvModel, arguments);
|
||||
self.srvModel = model;
|
||||
});
|
||||
eventAggregator.$on("connection-pending", function () {
|
||||
|
@ -13,6 +13,9 @@ var hubListener = function () {
|
||||
connection.on("InvoiceCreated", function (invoiceId) {
|
||||
eventAggregator.$emit("invoice-created", invoiceId);
|
||||
});
|
||||
connection.on("InvoiceConfirmed", function (invoiceId) {
|
||||
eventAggregator.$emit("invoice-confirmed", invoiceId);
|
||||
});
|
||||
connection.on("InvoiceError", function (error) {
|
||||
eventAggregator.$emit("invoice-error", error);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user