mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Payment Request: Improve public view (#5413)
* Payment Request: Improve public view Closes #4450. * Test fix * Extract Vue utils * Improve payment history * Fix amount display * Unify receipt and payment request tables * Re-add text confirmation for copying to clipboard * Minor print optimizations * Wording: Rename Description to Memo * Open view links in new window * View updates
This commit is contained in:
parent
46f0818765
commit
2fb72d5aa6
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="118.0.5993.7000" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="119.0.6045.10500" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -92,9 +92,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
@ -103,6 +102,9 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.PayInvoice(true);
|
||||
var invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToInvoice(invoiceId);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
|
||||
@ -116,13 +118,19 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||
var editUrl = s.Driver.Url;
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='form-button']")).Click();
|
||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||
invoiceId = s.Driver.Url.Split('/').Last();
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.Driver.Navigate().GoToUrl(editUrl);
|
||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
@ -1193,13 +1201,13 @@ namespace BTCPayServer.Tests
|
||||
var editUrl = s.Driver.Url;
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
var viewUrl = s.Driver.Url;
|
||||
|
||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.Id("PayInvoice")).Text.Trim());
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// expire
|
||||
s.GoToUrl(editUrl);
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||
@ -1219,12 +1227,28 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(s.Driver.FindElement(By.Id("Currency")).Enabled);
|
||||
|
||||
s.GoToUrl(viewUrl);
|
||||
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.Id("PayInvoice")).Text.Trim());
|
||||
|
||||
// test invoice creation, click with JS, because the button is inside a sticky header
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
// test invoice creation
|
||||
s.Driver.FindElement(By.Id("PayInvoice")).Click();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.WaitUntilAvailable(By.Name("btcpay"));
|
||||
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
Assert.True(frameElement.Displayed);
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
IWebElement closebutton = null;
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
closebutton = iframe.FindElement(By.Id("close"));
|
||||
Assert.True(closebutton.Displayed);
|
||||
});
|
||||
closebutton.Click();
|
||||
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||
});
|
||||
|
||||
// amount and currency should not be editable, because invoice exists
|
||||
s.GoToUrl(editUrl);
|
||||
@ -1248,32 +1272,45 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// payment
|
||||
s.GoToUrl(viewUrl);
|
||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
||||
|
||||
// Pay full amount
|
||||
s.PayInvoice();
|
||||
|
||||
// Processing
|
||||
s.Driver.FindElement(By.Id("PayInvoice")).Click();
|
||||
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.Driver.WaitUntilAvailable(By.Name("btcpay"));
|
||||
|
||||
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
|
||||
Assert.True(frameElement.Displayed);
|
||||
var iframe = s.Driver.SwitchTo().Frame(frameElement);
|
||||
iframe.WaitUntilAvailable(By.Id("Checkout-v2"));
|
||||
|
||||
// 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();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles[0]);
|
||||
Assert.Equal("Processing", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||
s.Driver.SwitchTo().Frame(frameElement);
|
||||
|
||||
// Mine
|
||||
s.MineBlockOnInvoiceCheckout();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
// Mine
|
||||
s.MineBlockOnInvoiceCheckout();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
Assert.Contains("Mined 1 block",
|
||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||
});
|
||||
|
||||
s.Driver.FindElement(By.Id("close")).Click();
|
||||
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||
});
|
||||
s.GoToUrl(viewUrl);
|
||||
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles[0]);
|
||||
Assert.Equal("Settled", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||
}
|
||||
|
||||
@ -1769,7 +1806,12 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Contains("PP1", s.Driver.PageSource);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
@ -1780,9 +1822,9 @@ namespace BTCPayServer.Tests
|
||||
var description = s.Driver.FindElement(By.ClassName("card-block"));
|
||||
description.SendKeys("Description Edit");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("PP1 Edited")).Click();
|
||||
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Contains("Description Edit", s.Driver.PageSource);
|
||||
Assert.Contains("PP1 Edited", s.Driver.PageSource);
|
||||
}
|
||||
@ -1808,7 +1850,12 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.Contains("PP1", s.Driver.PageSource);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
|
||||
@ -1820,6 +1867,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// This should select the first View, ie, the last one PP2
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
@ -1841,6 +1889,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
|
||||
|
||||
var viewPullPaymentUrl = s.Driver.Url;
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// This one should have nothing
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
@ -1918,8 +1969,10 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys(Keys.Enter);
|
||||
@ -1932,6 +1985,8 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
||||
s.FindAlertMessage();
|
||||
var tx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.Payouts);
|
||||
|
||||
@ -1970,6 +2025,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
// Bitcoin-only, SelectedPaymentMethod should not be displayed
|
||||
s.Driver.ElementDoesNotExist(By.Id("SelectedPaymentMethod"));
|
||||
@ -1993,6 +2049,8 @@ namespace BTCPayServer.Tests
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
@ -2000,10 +2058,10 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click();
|
||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||
Assert.Contains(bolt, s.Driver.PageSource);
|
||||
Assert.Contains($"{payoutAmount.ToString()} BTC", s.Driver.PageSource);
|
||||
Assert.Contains($"{payoutAmount} BTC", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
||||
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
||||
|
||||
@ -2028,16 +2086,21 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// LNURL Withdraw support check with BTC denomination
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
@ -2048,8 +2111,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
||||
s.Driver.WaitForElement(By.Id("qr-code-data-input"));
|
||||
|
||||
@ -2075,6 +2141,8 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
|
||||
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
||||
});
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
@ -2084,8 +2152,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("0.0000001");
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
||||
lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
|
||||
|
||||
@ -2109,6 +2180,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
||||
});
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
|
||||
// LNURL Withdraw support check with SATS denomination
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
@ -2119,8 +2192,11 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("21021");
|
||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("SATS" + Keys.Enter);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage();
|
||||
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
||||
lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
|
||||
s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click();
|
||||
@ -2145,6 +2221,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
|
||||
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
||||
});
|
||||
s.Driver.Close();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -2534,13 +2611,16 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
||||
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
var pullPaymentId = s.Driver.Url.Split('/').Last();
|
||||
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(lnurl);
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
||||
s.FindAlertMessage();
|
||||
|
||||
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||
payouts[0].Click();
|
||||
|
@ -13,14 +13,20 @@ namespace BTCPayServer
|
||||
{
|
||||
return Regex.Match(color, Pattern).Success;
|
||||
}
|
||||
public string TextColor(string bgColor)
|
||||
|
||||
public Color TextColor(Color bg)
|
||||
{
|
||||
int nThreshold = 105;
|
||||
var bg = ColorTranslator.FromHtml(bgColor);
|
||||
int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + (bg.B * 0.114));
|
||||
Color color = (255 - bgDelta < nThreshold) ? Color.Black : Color.White;
|
||||
int bgDelta = Convert.ToInt32(bg.R * 0.299 + bg.G * 0.587 + bg.B * 0.114);
|
||||
return 255 - bgDelta < nThreshold ? Color.Black : Color.White;
|
||||
}
|
||||
|
||||
public string TextColor(string bg)
|
||||
{
|
||||
var color = TextColor(FromHtml(bg));
|
||||
return ColorTranslator.ToHtml(color).ToLowerInvariant();
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
public static readonly ColorPalette Default = new ColorPalette(new string[] {
|
||||
"#fbca04",
|
||||
@ -31,6 +37,7 @@ namespace BTCPayServer
|
||||
"#cdcdcd",
|
||||
"#cc317c",
|
||||
});
|
||||
|
||||
private ColorPalette(string[] labels)
|
||||
{
|
||||
Labels = labels;
|
||||
@ -98,5 +105,10 @@ namespace BTCPayServer
|
||||
var color = AdjustBrightness(ColorTranslator.FromHtml(html), correctionFactor);
|
||||
return ColorTranslator.ToHtml(color);
|
||||
}
|
||||
|
||||
public Color FromHtml(string html)
|
||||
{
|
||||
return ColorTranslator.FromHtml(html);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ namespace BTCPayServer.Controllers
|
||||
Amount = paymentEntity.PaidAmount.Gross,
|
||||
Paid = paymentEntity.InvoicePaidAmount.Net,
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
||||
AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency, DisplayFormatter.CurrencyFormat.None),
|
||||
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||
|
@ -205,6 +205,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
vm.StoreName = store.StoreName;
|
||||
vm.StoreWebsite = store.StoreWebsite;
|
||||
vm.BrandColor = storeBlob.BrandColor;
|
||||
vm.LogoFileId = storeBlob.LogoFileId;
|
||||
vm.CssFileId = storeBlob.CssFileId;
|
||||
|
@ -72,7 +72,11 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
|
||||
[Display(Name = "Expiration Date")]
|
||||
public DateTime? ExpiryDate { get; set; }
|
||||
[Required] public string Title { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Display(Name = "Memo")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Display(Name = "Store")]
|
||||
@ -87,7 +91,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
|
||||
[Display(Name = "Custom CSS Code")]
|
||||
public string EmbeddedCSS { get; set; }
|
||||
[Display(Name = "Allow payee to create invoices in their own denomination")]
|
||||
|
||||
[Display(Name = "Allow payee to create invoices with custom amounts")]
|
||||
public bool AllowCustomPaymentAmounts { get; set; }
|
||||
|
||||
public Dictionary<string, object> FormResponse { get; set; }
|
||||
@ -151,6 +156,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
public string CssFileId { get; set; }
|
||||
public string BrandColor { get; set; }
|
||||
public string StoreName { get; set; }
|
||||
public string StoreWebsite { get; set; }
|
||||
public string EmbeddedCSS { get; set; }
|
||||
public string CustomCSSLink { get; set; }
|
||||
|
||||
@ -208,6 +214,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
{
|
||||
public string PaymentMethod { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string AmountFormatted { get; set; }
|
||||
public string RateFormatted { get; set; }
|
||||
public decimal Paid { get; set; }
|
||||
public string PaidFormatted { get; set; }
|
||||
|
@ -98,9 +98,13 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Display(Name = "Memo")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Display(Name = "Custom CSS URL")]
|
||||
public string CustomCSSLink { get; set; }
|
||||
|
||||
[Display(Name = "Custom CSS Code")]
|
||||
public string EmbeddedCSS { get; set; }
|
||||
}
|
||||
|
@ -135,6 +135,7 @@ namespace BTCPayServer.PaymentRequest
|
||||
Amount = paymentEntity.PaidAmount.Gross,
|
||||
Paid = paymentEntity.InvoicePaidAmount.Net,
|
||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
||||
AmountFormatted = _displayFormatter.Currency(paymentEntity.PaidAmount.Gross, paymentEntity.PaidAmount.Currency, DisplayFormatter.CurrencyFormat.None),
|
||||
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||
|
@ -10,20 +10,30 @@
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.BrandColor))
|
||||
{
|
||||
var brandColor = Model.BrandColor;
|
||||
var accentColor = ColorPalette.Default.AdjustBrightness(brandColor, (float)-0.15);
|
||||
var complement = ColorPalette.Default.TextColor(brandColor).ToLowerInvariant();
|
||||
var complementColor = $"var(--btcpay-{(complement == "black" ? "black" : "white")})";
|
||||
var brand = Model.BrandColor;
|
||||
var brandColor = ColorPalette.Default.FromHtml(brand);
|
||||
var brandRgbValues = $"{brandColor.R}, {brandColor.G}, {brandColor.B}";
|
||||
var accent = ColorPalette.Default.AdjustBrightness(brand, (float)-0.15);
|
||||
var complement = ColorPalette.Default.TextColor(brand);
|
||||
var complementVar = $"var(--btcpay-{(complement == "black" ? "black" : "white")})";
|
||||
<style>
|
||||
:root {
|
||||
--btcpay-primary: @brandColor;
|
||||
--btcpay-primary-shadow: @brandColor;
|
||||
--btcpay-primary-bg-hover: @accentColor;
|
||||
--btcpay-primary-bg-active: @accentColor;
|
||||
--btcpay-body-link-accent: @accentColor;
|
||||
--btcpay-primary-text: @complementColor;
|
||||
--btcpay-primary-text-hover: @complementColor;
|
||||
--btcpay-primary-text-active: @complementColor;
|
||||
--btcpay-primary: @brand;
|
||||
--btcpay-primary-rgb: @brandRgbValues;
|
||||
--btcpay-primary-shadow: @brand;
|
||||
--btcpay-primary-bg-hover: @accent;
|
||||
--btcpay-primary-bg-active: @accent;
|
||||
--btcpay-body-link: @brand;
|
||||
--btcpay-body-link-accent: @accent;
|
||||
--btcpay-primary-text: @complementVar;
|
||||
--btcpay-primary-text-hover: @complementVar;
|
||||
--btcpay-primary-text-active: @complementVar;
|
||||
}
|
||||
a {
|
||||
color: var(--btcpay-body-link);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--btcpay-body-link-accent);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
@ -303,6 +303,7 @@
|
||||
<script src="~/vendor/i18next/i18nextHttpBackend.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/i18next/vue-i18next.js" asp-append-version="true"></script>
|
||||
<script src="~/js/copy-to-clipboard.js" asp-append-version="true"></script>
|
||||
<script src="~/js/vue-utils.js" asp-append-version="true"></script>
|
||||
<script src="~/main/utils.js" asp-append-version="true"></script>
|
||||
<script src="~/checkout-v2/checkout.js" asp-append-version="true"></script>
|
||||
@if (Env.CheatMode)
|
||||
|
@ -108,23 +108,21 @@
|
||||
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
||||
<h2 class="h4 mb-3">Payment Details</h2>
|
||||
<div class="table-responsive my-0 d-print-none">
|
||||
<table class="table table-borderless my-0">
|
||||
<table class="invoice table table-borderless">
|
||||
<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>
|
||||
<th class="fw-normal text-secondary date-col w-125px">Date</th>
|
||||
<th class="fw-normal text-secondary amount-col">Paid</th>
|
||||
<th class="fw-normal text-secondary amount-col w-225px">Payment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var payment in Model.Payments)
|
||||
{
|
||||
<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>
|
||||
<td class="date-col">@payment.ReceivedDate.ToBrowserDate()</td>
|
||||
<td class="amount-col">@payment.PaidFormatted</td>
|
||||
<td class="amount-col">@payment.AmountFormatted @payment.PaymentMethod</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(payment.Destination))
|
||||
{
|
||||
@ -132,7 +130,7 @@
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Destination
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<td class="fw-normal" colspan="2">
|
||||
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
@ -143,20 +141,20 @@
|
||||
<th class="fw-normal text-nowrap text-secondary">
|
||||
Payment Proof
|
||||
</th>
|
||||
<td class="fw-normal" colspan="3">
|
||||
<td class="fw-normal" colspan="2">
|
||||
<vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-none d-print-block">
|
||||
@foreach (var payment in Model.Payments)
|
||||
{
|
||||
<div class="mb-4">
|
||||
<strong>@payment.PaidFormatted</strong> = @payment.Amount @payment.PaymentMethod, Rate: @payment.RateFormatted
|
||||
<strong>@payment.PaidFormatted</strong> = @payment.AmountFormatted @payment.PaymentMethod, Rate: @payment.RateFormatted
|
||||
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||
{
|
||||
<div>Proof: @payment.PaymentProof</div>
|
||||
|
@ -45,7 +45,7 @@ else
|
||||
@foreach (var payment in Model.Payments)
|
||||
{
|
||||
<p> </p>
|
||||
<p class="text-center">@payment.Amount <span class="text-nowrap">@payment.PaymentMethod</span></p>
|
||||
<p class="text-center">@payment.AmountFormatted <span class="text-nowrap">@payment.PaymentMethod</span></p>
|
||||
<p class="text-center">Rate: @payment.RateFormatted</p>
|
||||
<p class="text-center">= @payment.PaidFormatted</p>
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
else
|
||||
{
|
||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton">Save</button>
|
||||
<a class="btn btn-secondary" asp-action="ViewPaymentRequest" asp-route-payReqId="@Model.Id" id="ViewPaymentRequest">View</a>
|
||||
<a class="btn btn-secondary" asp-action="ViewPaymentRequest" asp-route-payReqId="@Model.Id" id="ViewPaymentRequest" target="_blank">View</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -111,7 +111,7 @@
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="d-inline-flex align-items-center gap-3">
|
||||
<a asp-action="ViewPaymentRequest" asp-route-payReqId="@item.Id" id="PaymentRequest-@item.Id">View</a>
|
||||
<a asp-action="ViewPaymentRequest" asp-route-payReqId="@item.Id" id="PaymentRequest-@item.Id" target="_blank">View</a>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link dropdown-toggle p-0 dropdown-toggle-no-caret text-body" type="button" data-bs-toggle="dropdown" aria-expanded="false" id="ToggleActions-@item.Id">
|
||||
<vc:icon symbol="dots" />
|
||||
|
@ -36,14 +36,13 @@
|
||||
<partial name="LayoutHead" />
|
||||
<partial name="LayoutHeadStoreBranding" model="@(Model.BrandColor, Model.CssFileId, Model.CustomCSSLink, Model.EmbeddedCSS)" />
|
||||
<link href="~/vendor/bootstrap-vue/bootstrap-vue.min.css" asp-append-version="true" rel="stylesheet" />
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Safe.Json(Model);
|
||||
</script>
|
||||
<script>var srvModel = @Safe.Json(Model);</script>
|
||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vue-toasted/vue-toasted.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/bootstrap-vue/bootstrap-vue.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/signalr/signalr.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/animejs/anime.min.js" asp-append-version="true"></script>
|
||||
<script src="~/js/vue-utils.js" asp-append-version="true"></script>
|
||||
<script src="~/payment-request/app.js" asp-append-version="true"></script>
|
||||
<script src="~/payment-request/services/listener.js" asp-append-version="true"></script>
|
||||
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
|
||||
@ -51,6 +50,8 @@
|
||||
.invoice { margin-top: var(--btcpay-space-s); }
|
||||
.invoice + .invoice { margin-top: var(--btcpay-space-m); }
|
||||
.invoice .badge { font-size: var(--btcpay-font-size-s); }
|
||||
#app { --wrap-max-width: 720px; }
|
||||
#InvoiceDescription > :last-child { margin-bottom: 0; }
|
||||
|
||||
@@media print {
|
||||
@* This is to avoid table header showing up twice: https://github.com/btcpayserver/btcpayserver/issues/4341 *@
|
||||
@ -59,119 +60,108 @@
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-vh-100">
|
||||
<div id="app" class="d-flex flex-column min-vh-100 pb-l">
|
||||
<nav class="btcpay-header navbar sticky-top py-3 py-lg-4 d-print-block">
|
||||
<div class="container">
|
||||
<div class="row align-items-center" style="width:calc(100% + 30px)">
|
||||
<div class="col-12 col-md-8 col-lg-9">
|
||||
<div class="row">
|
||||
<div class="col col-12 col-lg-8">
|
||||
<h1 class="h3" v-text="srvModel.title"></h1>
|
||||
</div>
|
||||
<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-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 fw-semibold d-none d-lg-inline-block d-print-none border-0 p-0 ms-4 only-for-js" v-on:click="window.print" v-cloak>
|
||||
Print
|
||||
</button>
|
||||
<button type="button" class="btn btn-link fw-semibold d-none d-lg-inline-block d-print-none border-0 p-0 ms-4 only-for-js" v-on:click="window.copyUrlToClipboard" v-cloak>
|
||||
Copy Link
|
||||
</button>
|
||||
</div>
|
||||
<div class="col col-12 col-sm-6 text-sm-end col-lg-4 mt-lg-n4 pt-lg-1 d-print-none">
|
||||
@if (Model.IsPending && !Model.Archived && Model.ExpiryDate.HasValue)
|
||||
{
|
||||
<noscript>@Model.Status</noscript>
|
||||
}
|
||||
<template v-if="srvModel.isPending && !srvModel.archived && endDiff">
|
||||
<span class="text-muted">Expires in</span> {{endDiff}}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 pt-3 pb-2 col-md-4 py-md-0 col-lg-3">
|
||||
<noscript>
|
||||
@if (Model.IsPending && !Model.Archived)
|
||||
<div id="app" class="public-page-wrap">
|
||||
<main class="flex-grow-1">
|
||||
<div class="d-flex flex-column justify-content-center gap-4">
|
||||
<partial name="_StoreHeader" model="(Model.Title, Model.LogoFileId)" />
|
||||
<div class="text-center mt-n3">
|
||||
Invoice from
|
||||
@if (!string.IsNullOrEmpty(Model.StoreWebsite))
|
||||
{
|
||||
<a href="@Model.StoreWebsite" target="_blank" rel="noreferrer noopener">@Model.StoreName</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.StoreName
|
||||
}
|
||||
</div>
|
||||
<partial name="_StatusMessage" />
|
||||
<section class="tile">
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center justify-content-between mb-2">
|
||||
<h2 class="mb-0" v-text="srvModel.amountDue > 0 ? srvModel.amountDueFormatted : srvModel.amountCollectedFormatted">
|
||||
@if (Model.AmountDue > 0)
|
||||
{
|
||||
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
|
||||
{
|
||||
<form method="get" asp-action="PayPaymentRequest" asp-route-payReqId="@Model.Id" class="d-print-none">
|
||||
<div class="row">
|
||||
<div class="col col-12 col-sm-6 col-md-12">
|
||||
<div class="input-group">
|
||||
<input type="number" inputmode="decimal" class="form-control text-end hide-number-spin" name="amount" value="@Model.AmountDue" @if (!Model.AllowCustomPaymentAmounts) { @("readonly") } max="@Model.AmountDue" step="any" placeholder="Amount" required />
|
||||
<span class="input-group-text">@Model.Currency.ToUpper()</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
||||
<button class="btn btn-primary w-100 text-nowrap" type="submit" data-test="pay-button">Pay Invoice</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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-payReqId="@Model.Id" data-test="pay-button">
|
||||
Pay Invoice
|
||||
</a>
|
||||
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments && Model.AllowCustomPaymentAmounts)
|
||||
{
|
||||
<form method="get" asp-action="CancelUnpaidPendingInvoice" asp-route-payReqId="@Model.Id" class="mt-2 d-print-none">
|
||||
<button class="btn btn-outline-secondary w-100 text-nowrap" type="submit">Cancel Invoice</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@Model.AmountDueFormatted
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="h2 text-md-end">
|
||||
<span class="badge badge-@Model.Status.ToLowerInvariant()" data-test="status" style="font-size:.75em">
|
||||
@Model.Status
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<span>(archived)</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@Model.AmountCollectedFormatted
|
||||
}
|
||||
</noscript>
|
||||
<template v-if="srvModel.formId && srvModel.formId != 'None' && !srvModel.formSubmitted">
|
||||
<a asp-action="ViewPaymentRequestForm" asp-route-payReqId="@Model.Id" class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap btn-lg" data-test="form-button">
|
||||
</h2>
|
||||
<span class="badge only-for-js" :class="`badge-${srvModel.status.toLowerCase()}`" data-test="status" style="font-size:.9rem" v-if="srvModel.status.toLowerCase() !== 'pending'">
|
||||
{{srvModel.status}}
|
||||
<span v-if="srvModel.archived">(archived)</span>
|
||||
</span>
|
||||
@if (Model.Status.ToLowerInvariant() != "pending")
|
||||
{
|
||||
<noscript>
|
||||
<span class="badge badge-@Model.Status.ToLowerInvariant()" data-test="status" style="font-size:.9rem">
|
||||
@Model.Status
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<span>(archived)</span>
|
||||
}
|
||||
</span>
|
||||
</noscript>
|
||||
}
|
||||
</div>
|
||||
<p>
|
||||
@if (Model.IsPending && Model.ExpiryDate.HasValue)
|
||||
{
|
||||
<span class="text-muted">Due</span>
|
||||
<span>@Model.ExpiryDate.Value.ToBrowserDate(ViewsRazor.DateDisplayFormat.Relative)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">No due date</span>
|
||||
}
|
||||
</p>
|
||||
<dl class="mt-n1 mb-4" v-if="srvModel.amountCollected > 0 && srvModel.amountDue > 0">
|
||||
<div class="progress bg-light d-flex mb-3 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>
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center justify-content-between">
|
||||
<div class="d-flex d-print-inline-block flex-column gap-1">
|
||||
<dd class="text-secondary mb-0">Amount paid</dd>
|
||||
<dt class="h4 fw-semibold text-nowrap" v-text="srvModel.amountCollectedFormatted">@Model.AmountCollectedFormatted</dt>
|
||||
</div>
|
||||
<div class="d-flex d-print-inline-block flex-column gap-1">
|
||||
<dd class="text-secondary mb-0 text-sm-end">Total requested</dd>
|
||||
<dt class="h4 fw-semibold text-nowrap text-sm-end" v-text="srvModel.amountFormatted">@Model.AmountFormatted</dt>
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div class="buttons mt-3">
|
||||
<template v-if="srvModel.formId && srvModel.formId !== 'None' && !srvModel.formSubmitted">
|
||||
<a asp-action="ViewPaymentRequestForm" asp-route-payReqId="@Model.Id" class="btn btn-primary btn-lg" data-test="form-button">
|
||||
Pay Invoice
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="srvModel.isPending && !srvModel.archived">
|
||||
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
|
||||
<form v-on:submit="submitCustomAmountForm" class="d-print-none">
|
||||
<div class="row">
|
||||
<div class="col col-12 col-sm-6 col-md-12">
|
||||
<div class="input-group">
|
||||
<input type="number" inputmode="decimal" class="form-control text-end hide-number-spin" v-model="customAmount" :readonly="!srvModel.allowCustomPaymentAmounts" :max="srvModel.amountDue" placeholder="Amount" step="any" required />
|
||||
<span class="input-group-text">{{currency}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-2 col-12 col-sm-6 mt-sm-0 col-md-12 mt-md-2">
|
||||
<button class="btn btn-primary w-100 d-flex d-print-none align-items-center justify-content-center text-nowrap" v-bind:class="{ 'btn-disabled': loading}" :disabled="loading" type="submit" data-test="pay-button">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
Pay Invoice
|
||||
</button>
|
||||
</div>
|
||||
<form v-on:submit="submitCustomAmountForm">
|
||||
<div class="input-group mb-3">
|
||||
<input type="number" class="form-control text-end hide-number-spin" v-model="customAmount" :readonly="!srvModel.allowCustomPaymentAmounts" :max="srvModel.amountDue" placeholder="Amount" step="any" required />
|
||||
<span class="input-group-text">{{currency}}</span>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg w-100 d-flex align-items-center justify-content-center text-nowrap" v-bind:class="{ 'btn-disabled': loading }" :disabled="loading" type="submit" id="PayInvoice">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
Pay Invoice
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<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) || !srvModel.allowCustomPaymentAmounts}" v-on:click="pay(null)" :disabled="loading" data-test="pay-button">
|
||||
<button class="btn btn-primary btn-lg w-100 d-flex align-items-center justify-content-center text-nowrap" v-on:click="pay(null)" :disabled="loading" id="PayInvoice">
|
||||
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<span>Pay Invoice</span>
|
||||
</button>
|
||||
@if (Model.AllowCustomPaymentAmounts) {
|
||||
<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">
|
||||
<button class="btn btn-outline-secondary btn-lg w-100 d-flex 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 me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</span>
|
||||
@ -180,193 +170,150 @@
|
||||
}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="h2 text-md-end">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<div class="d-flex flex-column flex-sm-row gap-3 align-items-center justify-content-between">
|
||||
<button type="button" class="btn btn-secondary only-for-js w-100" v-on:click="window.print">
|
||||
Print
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary only-for-js w-100" v-on:click="window.copyUrlToClipboard">
|
||||
Copy Link
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow-1 py-4">
|
||||
<div class="container">
|
||||
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData){ { "Margin", "mb-4" } })" />
|
||||
<div class="row">
|
||||
<div class="col col-12 col-lg-6 mb-4">
|
||||
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
||||
<h2 class="h4 mb-3">Invoice Summary</h2>
|
||||
@if (!string.IsNullOrEmpty(Model.Description) && Model.Description != "<br>")
|
||||
<noscript>
|
||||
@if (Model.IsPending && !Model.Archived)
|
||||
{
|
||||
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
|
||||
{
|
||||
<div v-html="srvModel.description"></div>
|
||||
<form method="get" asp-action="PayPaymentRequest" asp-route-payReqId="@Model.Id">
|
||||
<div class="input-group mb-3">
|
||||
<input type="number" class="form-control text-end hide-number-spin" name="amount" value="@Model.AmountDue" @if (!Model.AllowCustomPaymentAmounts) { @("readonly") } max="@Model.AmountDue" step="any" placeholder="Amount" required />
|
||||
<span class="input-group-text">@Model.Currency.ToUpper()</span>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-lg w-100 text-nowrap" type="submit" id="PayInvoice">Pay Invoice</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-muted mt-3 mb-0">No details provided.</p>
|
||||
<a class="btn btn-primary btn-lg w-100 text-nowrap" asp-action="PayPaymentRequest" asp-route-payReqId="@Model.Id" id="PayInvoice">
|
||||
Pay Invoice
|
||||
</a>
|
||||
if (Model.AnyPendingInvoice && !Model.PendingInvoiceHasPayments && Model.AllowCustomPaymentAmounts)
|
||||
{
|
||||
<form method="get" asp-action="CancelUnpaidPendingInvoice" asp-route-payReqId="@Model.Id" class="mt-2 d-print-none">
|
||||
<button class="btn btn-outline-secondary btn-lg w-100 text-nowrap" type="submit">Cancel Invoice</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
</noscript>
|
||||
</section>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Description) && Model.Description != "<br>")
|
||||
{
|
||||
<section class="tile">
|
||||
<h2 class="h4 mb-3">Memo</h2>
|
||||
<div id="InvoiceDescription" v-html="srvModel.description">@Safe.Raw(Model.Description)</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section class="tile">
|
||||
<h2 class="h4 mb-3">Payment History</h2>
|
||||
<template v-if="!srvModel.invoices || srvModel.invoices.length == 0">
|
||||
<p class="text-muted mb-0">No payments have been made yet.</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="table-responsive my-0">
|
||||
<table v-for="invoice of srvModel.invoices" :key="invoice.id" class="invoice table table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fw-normal text-secondary" scope="col">Invoice Id</th>
|
||||
<th class="fw-normal text-secondary amount-col w-125px">Amount</th>
|
||||
<th class="fw-normal text-secondary text-end w-225px">Status</th>
|
||||
<th class="w-50px actions-col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="align-middle"><vc:truncate-center is-vue="true" text="invoice.id" padding="7" classes="truncate-center-id" /></td>
|
||||
<td class="align-middle amount-col">{{invoice.amountFormatted}}</td>
|
||||
<td class="align-middle text-end text-print-default">
|
||||
<span class="badge" :class="`badge-${statusClass(invoice.stateFormatted)}`">{{invoice.stateFormatted}}</span>
|
||||
</td>
|
||||
<td class="align-middle actions-col">
|
||||
<div class="d-inline-flex align-items-center gap-2">
|
||||
<button class="accordion-button collapsed only-for-js ms-0 d-inline-block" type="button" :aria-controls="`invoice_details_${invoice.id}`" :aria-expanded="showDetails(invoice.id) ? 'true' : 'false'" v-if="invoice.payments && invoice.payments.length > 0" v-on:click="toggleDetails(invoice.id)">
|
||||
<vc:icon symbol="caret-down" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-collapsible="showDetails(invoice.id)" :id="`invoice_details_${invoice.id}`" v-if="invoice.payments && invoice.payments.length > 0">
|
||||
<th class="fw-normal text-secondary">Transaction</th>
|
||||
<th class="fw-normal text-secondary amount-col">Paid</th>
|
||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
||||
</tr>
|
||||
<tr v-collapsible="showDetails(invoice.id)" v-for="payment of invoice.payments" :key="`invoice_payment_${payment.id}`">
|
||||
<td class="text-break"><vc:truncate-center is-vue="true" 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.amountFormatted}} {{payment.paymentMethod}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-12 col-lg-6 mb-4">
|
||||
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
||||
<h2 class="h4 mb-3">Payment Details</h2>
|
||||
<dl class="mb-0 mt-md-4">
|
||||
<div class="d-flex d-print-inline-block flex-column mb-4 me-5">
|
||||
<dt class="h4 fw-semibold text-nowrap text-primary text-print-default order-2 order-sm-1 mb-1" v-text="srvModel.amountDueFormatted">@Model.AmountDueFormatted</dt>
|
||||
<dd class="order-1 order-sm-2 mb-1" data-test="amount-due-title">Amount due</dd>
|
||||
</div>
|
||||
<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>
|
||||
<div class="d-flex d-print-inline-block flex-column mb-4 me-5 d-sm-inline-flex mb-sm-0">
|
||||
<dt class="h4 fw-semibold text-nowrap order-2 order-sm-1 mb-1" v-text="srvModel.amountCollectedFormatted">@Model.AmountCollectedFormatted</dt>
|
||||
<dd class="order-1 order-sm-2 mb-1">Amount paid</dd>
|
||||
</div>
|
||||
<div class="d-flex d-print-inline-block flex-column mb-0 d-sm-inline-flex float-sm-end">
|
||||
<dt class="h4 text-sm-end fw-semibold text-nowrap order-2 order-sm-1 mb-1" v-text="srvModel.amountFormatted">@Model.AmountFormatted</dt>
|
||||
<dd class="text-sm-end order-1 order-sm-2 mb-1">Total requested</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
|
||||
<h2 class="h4 mb-0">Payment History</h2>
|
||||
<noscript>
|
||||
@if (Model.Invoices == null || !Model.Invoices.Any())
|
||||
{
|
||||
<p class="text-muted mt-3 mb-0">No payments made yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var invoice in Model.Invoices)
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="invoice table">
|
||||
<thead>
|
||||
<tr class="table-borderless">
|
||||
<th class="fw-normal text-secondary w-350px" scope="col">Invoice Id</th>
|
||||
<th class="fw-normal text-secondary w-175px">Expiry</th>
|
||||
<th class="fw-normal text-secondary text-end w-125px">Amount</th>
|
||||
<th class="fw-normal text-secondary text-end w-125px"></th>
|
||||
<th class="fw-normal text-secondary text-end">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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 badge-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
||||
</td>
|
||||
</tr>
|
||||
@if (invoice.Payments != null && invoice.Payments.Any())
|
||||
{
|
||||
<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>
|
||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
||||
</tr>
|
||||
@foreach (var payment in invoice.Payments)
|
||||
{
|
||||
<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">
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</noscript>
|
||||
<template v-if="!srvModel.invoices || srvModel.invoices.length == 0">
|
||||
<p class="text-muted mt-3 mb-0">No payments made yet.</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="table-responsive">
|
||||
<table v-for="invoice of srvModel.invoices" :key="invoice.id" class="invoice table">
|
||||
</template>
|
||||
<noscript>
|
||||
@if (Model.Invoices == null || !Model.Invoices.Any())
|
||||
{
|
||||
<p class="text-muted mb-0">No payments have been made yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var invoice in Model.Invoices)
|
||||
{
|
||||
<div class="table-responsive my-0">
|
||||
<table class="invoice table table-borderless">
|
||||
<thead>
|
||||
<tr class="table-borderless">
|
||||
<th class="fw-normal text-secondary w-350px" scope="col">Invoice Id</th>
|
||||
<th class="fw-normal text-secondary w-175px">Expiry</th>
|
||||
<th class="fw-normal text-secondary text-end w-125px">Amount</th>
|
||||
<th class="fw-normal text-secondary text-end w-125px"></th>
|
||||
<tr>
|
||||
<th class="fw-normal text-secondary" scope="col">Invoice Id</th>
|
||||
<th class="fw-normal text-secondary amount-col w-125px">Amount</th>
|
||||
<th class="fw-normal text-secondary text-end">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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>
|
||||
<tr>
|
||||
<td><vc:truncate-center text="@invoice.Id" padding="7" classes="truncate-center-id" /></td>
|
||||
<td class="amount-col">@invoice.AmountFormatted</td>
|
||||
<td class="text-end text-print-default">
|
||||
<span class="badge" :class="`badge-${statusClass(invoice.stateFormatted)}`">{{invoice.stateFormatted}}</span>
|
||||
<span class="badge badge-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="invoice.payments && invoice.payments.length > 0">
|
||||
<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>
|
||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
||||
@if (invoice.Payments != null && invoice.Payments.Any())
|
||||
{
|
||||
<tr>
|
||||
<th class="fw-normal text-secondary">Transaction</th>
|
||||
<th class="fw-normal text-secondary amount-col">Paid</th>
|
||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
||||
</tr>
|
||||
<template v-for="payment of invoice.payments">
|
||||
<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>
|
||||
@foreach (var payment in invoice.Payments)
|
||||
{
|
||||
<tr>
|
||||
<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="text-end text-nowrap">@payment.AmountFormatted @payment.PaymentMethod</td>
|
||||
</tr>
|
||||
<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>
|
||||
<span v-else>{{payment.id}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</noscript>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="store-footer">
|
||||
<p permission="@Policies.CanModifyStoreSettings">
|
||||
<p permission="@Policies.CanModifyStoreSettings" class="d-print-none">
|
||||
<a asp-controller="UIPaymentRequest" asp-action="EditPaymentRequest" asp-route-storeId="@Model.StoreId" asp-route-payReqId="@Model.Id">
|
||||
Edit payment request
|
||||
</a>
|
||||
|
@ -26,7 +26,7 @@
|
||||
else
|
||||
{
|
||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton">Save</button>
|
||||
<a class="btn btn-secondary" asp-action="ViewPullPayment" asp-route-pullPaymentId="@Model.Id" id="ViewPullPayment">View</a>
|
||||
<a class="btn btn-secondary" asp-action="ViewPullPayment" asp-route-pullPaymentId="@Model.Id" id="ViewPullPayment" target="_blank">View</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -119,7 +119,7 @@
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Automatically Approved</th>
|
||||
<th scope="col">Refunded</th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col" class="actions-col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -146,11 +146,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<td class="actions-col">
|
||||
<div class="d-inline-flex align-items-center gap-3">
|
||||
<a asp-action="ViewPullPayment"
|
||||
asp-controller="UIPullPayment"
|
||||
asp-route-pullPaymentId="@pp.Id">
|
||||
asp-route-pullPaymentId="@pp.Id"
|
||||
target="_blank">
|
||||
View
|
||||
</a>
|
||||
<a class="pp-payout"
|
||||
|
@ -1,4 +1,4 @@
|
||||
:root {
|
||||
#Checkout-v2 {
|
||||
--navbutton-size: .8rem;
|
||||
--section-padding: 1.5rem;
|
||||
--border-radius: var(--btcpay-border-radius-l);
|
||||
|
@ -1,41 +1,3 @@
|
||||
Vue.directive('collapsible', {
|
||||
bind: function (el, binding) {
|
||||
el.classList.add('collapse');
|
||||
el.classList[binding.value ? 'add' : 'remove']('show');
|
||||
el.transitionDuration = 350;
|
||||
},
|
||||
update: function (el, binding) {
|
||||
if (binding.oldValue !== binding.value){
|
||||
if (binding.value) {
|
||||
setTimeout(function () {
|
||||
el.classList.remove('collapse');
|
||||
const height = window.getComputedStyle(el).height;
|
||||
el.classList.add('collapsing');
|
||||
el.offsetHeight;
|
||||
el.style.height = height;
|
||||
setTimeout(function () {
|
||||
el.classList.remove('collapsing');
|
||||
el.classList.add('collapse');
|
||||
el.style.height = null;
|
||||
el.classList.add('show');
|
||||
}, el.transitionDuration)
|
||||
}, 0);
|
||||
} else {
|
||||
el.style.height = window.getComputedStyle(el).height;
|
||||
el.classList.remove('collapse');
|
||||
el.classList.remove('show');
|
||||
el.offsetHeight;
|
||||
el.style.height = null;
|
||||
el.classList.add('collapsing');
|
||||
setTimeout(function () {
|
||||
el.classList.add('collapse');
|
||||
el.classList.remove('collapsing');
|
||||
}, el.transitionDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// These are the legacy states, see InvoiceEntity
|
||||
const STATUS_PAYABLE = ['new'];
|
||||
const STATUS_PAID = ['paid'];
|
||||
|
@ -1,7 +1,12 @@
|
||||
function confirmCopy(el, message) {
|
||||
const hasIcon = !!el.innerHTML.match('icon-copy')
|
||||
const confirmHTML = `<span class="text-success">${message}</span>`;
|
||||
if (hasIcon) {
|
||||
el.innerHTML = el.innerHTML.replace('#copy', '#checkmark');
|
||||
} else {
|
||||
el.dataset.clipboardInitial = el.innerHTML;
|
||||
el.style.minWidth = el.getBoundingClientRect().width + 'px';
|
||||
el.innerHTML = confirmHTML;
|
||||
}
|
||||
el.dataset.clipboardConfirming = true;
|
||||
if (el.dataset.clipboardHandler) {
|
||||
@ -10,6 +15,8 @@ function confirmCopy(el, message) {
|
||||
const timeoutId = setTimeout(function () {
|
||||
if (hasIcon) {
|
||||
el.innerHTML = el.innerHTML.replace('#checkmark', '#copy');
|
||||
} else if (el.innerHTML === confirmHTML) {
|
||||
el.innerHTML = el.dataset.clipboardInitial;
|
||||
}
|
||||
delete el.dataset.clipboardConfirming;
|
||||
el.dataset.clipboardHandler = null;
|
||||
|
37
BTCPayServer/wwwroot/js/vue-utils.js
Normal file
37
BTCPayServer/wwwroot/js/vue-utils.js
Normal file
@ -0,0 +1,37 @@
|
||||
Vue.directive('collapsible', {
|
||||
bind: function (el, binding) {
|
||||
el.classList.add('collapse');
|
||||
el.classList[binding.value ? 'add' : 'remove']('show');
|
||||
el.transitionDuration = 350;
|
||||
},
|
||||
update: function (el, binding) {
|
||||
if (binding.oldValue !== binding.value){
|
||||
if (binding.value) {
|
||||
setTimeout(function () {
|
||||
el.classList.remove('collapse');
|
||||
const height = window.getComputedStyle(el).height;
|
||||
el.classList.add('collapsing');
|
||||
el.offsetHeight;
|
||||
el.style.height = height;
|
||||
setTimeout(function () {
|
||||
el.classList.remove('collapsing');
|
||||
el.classList.add('collapse');
|
||||
el.style.height = null;
|
||||
el.classList.add('show');
|
||||
}, el.transitionDuration)
|
||||
}, 0);
|
||||
} else {
|
||||
el.style.height = window.getComputedStyle(el).height;
|
||||
el.classList.remove('collapse');
|
||||
el.classList.remove('show');
|
||||
el.offsetHeight;
|
||||
el.style.height = null;
|
||||
el.classList.add('collapsing');
|
||||
setTimeout(function () {
|
||||
el.classList.add('collapse');
|
||||
el.classList.remove('collapsing');
|
||||
}, el.transitionDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -219,7 +219,8 @@ h2 svg.icon.icon-info {
|
||||
|
||||
/* Print */
|
||||
@media print {
|
||||
section {
|
||||
section,
|
||||
.tile {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.table-responsive {
|
||||
@ -245,17 +246,21 @@ h2 svg.icon.icon-info {
|
||||
a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.buttons,
|
||||
.toasted-container {
|
||||
display: none !important;
|
||||
}
|
||||
.truncate-center a,
|
||||
.truncate-center button,
|
||||
.truncate-center-truncated {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
.card {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.actions-col {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Richtext editor */
|
||||
@ -638,28 +643,56 @@ input:checked + label.btcpay-list-select-item {
|
||||
}
|
||||
|
||||
/* Public pages */
|
||||
.public-page-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--btcpay-space-l) var(--btcpay-space-m);
|
||||
}
|
||||
|
||||
/* gradually try to set better but less supported values and units */
|
||||
.min-vh-100,
|
||||
.public-page-wrap {
|
||||
min-height: -webkit-fill-available !important;
|
||||
min-height: 100dvh !important;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
@media screen {
|
||||
.public-page-wrap {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
--wrap-max-width: none;
|
||||
--wrap-padding-vertical: var(--btcpay-space-l);
|
||||
--wrap-padding-horizontal: var(--btcpay-space-m);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
max-width: var(--wrap-max-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--wrap-padding-vertical) var(--wrap-padding-horizontal);
|
||||
}
|
||||
|
||||
/* gradually try to set better but less supported values and units */
|
||||
.min-vh-100,
|
||||
.public-page-wrap {
|
||||
min-height: -webkit-fill-available !important;
|
||||
min-height: 100dvh !important;
|
||||
}
|
||||
.tile {
|
||||
--section-padding: 1.5rem;
|
||||
--section-border-radius: var(--btcpay-border-radius-l);
|
||||
|
||||
padding: var(--section-padding);
|
||||
background: var(--btcpay-bg-tile);
|
||||
border-radius: var(--section-border-radius);
|
||||
box-shadow: var(--btcpay-box-shadow-lg);
|
||||
}
|
||||
.tile .buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--btcpay-space-m);
|
||||
}
|
||||
.tile > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.public-page-wrap {
|
||||
--wrap-padding-horizontal: 0;
|
||||
}
|
||||
.tile {
|
||||
--section-padding: 1rem;
|
||||
--section-border-radius: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Store Header */
|
||||
.store-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -705,6 +738,7 @@ a.store-powered-by svg {
|
||||
height: 2rem;
|
||||
width: 4rem;
|
||||
}
|
||||
.store-footer a:hover,
|
||||
a.store-powered-by:hover {
|
||||
color: var(--btcpay-body-text-hover);
|
||||
}
|
||||
@ -952,6 +986,11 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.truncate-center-end,
|
||||
.truncate-center-start {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.truncate-center-id {
|
||||
font-family: var(--btcpay-font-monospace);
|
||||
font-size: .875em;
|
||||
@ -1064,6 +1103,9 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.actions-col {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Mass Actions */
|
||||
.mass-action-head,
|
||||
|
@ -16,7 +16,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
active: true,
|
||||
loading: false,
|
||||
timeoutState: "",
|
||||
customAmount: null
|
||||
customAmount: null,
|
||||
detailsShown: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -109,6 +110,15 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
||||
default:
|
||||
return status.toLowerCase();
|
||||
}
|
||||
},
|
||||
showDetails(invoiceId) {
|
||||
return this.detailsShown[invoiceId] === true;
|
||||
},
|
||||
toggleDetails(invoiceId) {
|
||||
if (this.detailsShown[invoiceId])
|
||||
Vue.delete(this.detailsShown, invoiceId);
|
||||
else
|
||||
Vue.set(this.detailsShown, invoiceId, true);
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
|
Loading…
Reference in New Issue
Block a user