mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +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:
@@ -23,7 +23,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||||
<PackageReference Include="Selenium.WebDriver" 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" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -92,9 +92,8 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||||
var windows = s.Driver.WindowHandles;
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
Assert.Equal(2, windows.Count);
|
|
||||||
s.Driver.SwitchTo().Window(windows[1]);
|
|
||||||
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
s.Driver.FindElement(By.CssSelector("button[type='submit']")).Click();
|
||||||
|
|
||||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||||
@@ -103,6 +102,9 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
s.PayInvoice(true);
|
s.PayInvoice(true);
|
||||||
var invoiceId = s.Driver.Url[(s.Driver.Url.LastIndexOf("/", StringComparison.Ordinal) + 1)..];
|
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);
|
s.GoToInvoice(invoiceId);
|
||||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
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();
|
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).Click();
|
||||||
var editUrl = s.Driver.Url;
|
var editUrl = s.Driver.Url;
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
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();
|
s.Driver.FindElement(By.CssSelector("[data-test='form-button']")).Click();
|
||||||
Assert.Contains("Enter your email", s.Driver.PageSource);
|
Assert.Contains("Enter your email", s.Driver.PageSource);
|
||||||
|
|
||||||
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
|
||||||
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
|
||||||
invoiceId = s.Driver.Url.Split('/').Last();
|
invoiceId = s.Driver.Url.Split('/').Last();
|
||||||
|
s.Driver.Close();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
s.Driver.Navigate().GoToUrl(editUrl);
|
s.Driver.Navigate().GoToUrl(editUrl);
|
||||||
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
Assert.Contains("aa@aa.com", s.Driver.PageSource);
|
||||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||||
@@ -1193,13 +1201,13 @@ namespace BTCPayServer.Tests
|
|||||||
var editUrl = s.Driver.Url;
|
var editUrl = s.Driver.Url;
|
||||||
|
|
||||||
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
s.Driver.FindElement(By.Id("ViewPaymentRequest")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
var viewUrl = s.Driver.Url;
|
var viewUrl = s.Driver.Url;
|
||||||
|
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.Id("PayInvoice")).Text.Trim());
|
||||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
s.Driver.Close();
|
||||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
// expire
|
// expire
|
||||||
s.GoToUrl(editUrl);
|
|
||||||
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
s.Driver.ExecuteJavaScript("document.getElementById('ExpiryDate').value = '2021-01-21T21:00:00.000Z'");
|
||||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||||
s.Driver.FindElement(By.XPath("//a[starts-with(@id, 'Edit-')]")).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);
|
Assert.True(s.Driver.FindElement(By.Id("Currency")).Enabled);
|
||||||
|
|
||||||
s.GoToUrl(viewUrl);
|
s.GoToUrl(viewUrl);
|
||||||
s.Driver.AssertElementNotFound(By.CssSelector("[data-test='status']"));
|
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.Id("PayInvoice")).Text.Trim());
|
||||||
Assert.Equal("Pay Invoice", s.Driver.FindElement(By.CssSelector("[data-test='pay-button']")).Text.Trim());
|
|
||||||
|
|
||||||
// test invoice creation, click with JS, because the button is inside a sticky header
|
// test invoice creation
|
||||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
s.Driver.FindElement(By.Id("PayInvoice")).Click();
|
||||||
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
|
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
|
// amount and currency should not be editable, because invoice exists
|
||||||
s.GoToUrl(editUrl);
|
s.GoToUrl(editUrl);
|
||||||
@@ -1248,7 +1272,15 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
// payment
|
// payment
|
||||||
s.GoToUrl(viewUrl);
|
s.GoToUrl(viewUrl);
|
||||||
s.Driver.ExecuteJavaScript("document.querySelector('[data-test=\"pay-button\"]').click()");
|
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"));
|
||||||
|
|
||||||
// Pay full amount
|
// Pay full amount
|
||||||
s.PayInvoice();
|
s.PayInvoice();
|
||||||
@@ -1262,9 +1294,9 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
|
Assert.Contains("Your payment has been received and is now processing", processingSection.Text);
|
||||||
});
|
});
|
||||||
|
|
||||||
s.GoToUrl(viewUrl);
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles[0]);
|
||||||
Assert.Equal("Processing", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
Assert.Equal("Processing", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
||||||
s.Driver.Navigate().Back();
|
s.Driver.SwitchTo().Frame(frameElement);
|
||||||
|
|
||||||
// Mine
|
// Mine
|
||||||
s.MineBlockOnInvoiceCheckout();
|
s.MineBlockOnInvoiceCheckout();
|
||||||
@@ -1273,7 +1305,12 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("Mined 1 block",
|
Assert.Contains("Mined 1 block",
|
||||||
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
s.Driver.WaitForElement(By.Id("CheatSuccessMessage")).Text);
|
||||||
});
|
});
|
||||||
s.GoToUrl(viewUrl);
|
|
||||||
|
s.Driver.FindElement(By.Id("close")).Click();
|
||||||
|
s.Driver.AssertElementNotFound(By.Name("btcpay"));
|
||||||
|
});
|
||||||
|
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles[0]);
|
||||||
Assert.Equal("Settled", s.Driver.WaitForElement(By.CssSelector("[data-test='status']")).Text);
|
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")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
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());
|
||||||
|
Assert.Contains("PP1", s.Driver.PageSource);
|
||||||
|
s.Driver.Close();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
|
|
||||||
@@ -1781,8 +1823,8 @@ namespace BTCPayServer.Tests
|
|||||||
description.SendKeys("Description Edit");
|
description.SendKeys("Description Edit");
|
||||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
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("Description Edit", s.Driver.PageSource);
|
||||||
Assert.Contains("PP1 Edited", 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")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
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());
|
||||||
|
Assert.Contains("PP1", s.Driver.PageSource);
|
||||||
|
s.Driver.Close();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
|
|
||||||
@@ -1820,6 +1867,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
// This should select the first View, ie, the last one PP2
|
// This should select the first View, ie, the last one PP2
|
||||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||||
@@ -1841,6 +1889,9 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
|
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
|
||||||
|
|
||||||
var viewPullPaymentUrl = s.Driver.Url;
|
var viewPullPaymentUrl = s.Driver.Url;
|
||||||
|
s.Driver.Close();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
|
|
||||||
// This one should have nothing
|
// This one should have nothing
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||||
@@ -1918,7 +1969,9 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
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();
|
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||||
@@ -1932,6 +1985,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click();
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
var tx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC));
|
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);
|
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("Currency")).SendKeys("BTC");
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
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());
|
||||||
|
|
||||||
// Bitcoin-only, SelectedPaymentMethod should not be displayed
|
// Bitcoin-only, SelectedPaymentMethod should not be displayed
|
||||||
s.Driver.ElementDoesNotExist(By.Id("SelectedPaymentMethod"));
|
s.Driver.ElementDoesNotExist(By.Id("SelectedPaymentMethod"));
|
||||||
@@ -1993,6 +2049,8 @@ namespace BTCPayServer.Tests
|
|||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
|
|
||||||
Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource);
|
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.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
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.ClassName("mass-action-select-all")).Click();
|
||||||
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click();
|
||||||
Assert.Contains(bolt, s.Driver.PageSource);
|
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.Driver.FindElement(By.CssSelector("#pay-invoices-form")).Submit();
|
||||||
|
|
||||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
s.FindAlertMessage();
|
||||||
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
s.GoToStore(newStore.storeId, StoreNavPages.Payouts);
|
||||||
s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click();
|
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.SetCheckbox(By.Id("AutoApproveClaims"), true);
|
||||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
|
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.FindElement(By.LinkText("View")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
|
||||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
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);
|
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
|
// LNURL Withdraw support check with BTC denomination
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
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("Amount")).SendKeys("0.0000001");
|
||||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC" + Keys.Enter);
|
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.FindElement(By.LinkText("View")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
|
||||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
||||||
s.Driver.WaitForElement(By.Id("qr-code-data-input"));
|
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.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
|
||||||
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
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.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
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("Amount")).SendKeys("0.0000001");
|
||||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC" + Keys.Enter);
|
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.FindElement(By.LinkText("View")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
|
||||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
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"));
|
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);
|
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
|
// LNURL Withdraw support check with SATS denomination
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
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("Amount")).SendKeys("21021");
|
||||||
s.Driver.FindElement(By.Id("Currency")).Clear();
|
s.Driver.FindElement(By.Id("Currency")).Clear();
|
||||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("SATS" + Keys.Enter);
|
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.FindElement(By.LinkText("View")).Click();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||||
|
|
||||||
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
|
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"));
|
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();
|
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.Contains(PayoutState.Completed.GetStateString(), s.Driver.PageSource);
|
||||||
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
Assert.Equal(LightningInvoiceStatus.Paid, (await s.Server.CustomerLightningD.GetInvoice(bolt2.Id)).Status);
|
||||||
});
|
});
|
||||||
|
s.Driver.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -2534,13 +2611,16 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
s.Driver.FindElement(By.Id("Create")).Click();
|
s.Driver.FindElement(By.Id("Create")).Click();
|
||||||
s.Driver.FindElement(By.LinkText("View")).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();
|
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")).Clear();
|
||||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("0.0000001" + Keys.Enter);
|
||||||
s.FindAlertMessage();
|
s.FindAlertMessage();
|
||||||
|
|
||||||
|
s.Driver.Close();
|
||||||
|
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
|
||||||
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
|
||||||
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
var payouts = s.Driver.FindElements(By.ClassName("pp-payout"));
|
||||||
payouts[0].Click();
|
payouts[0].Click();
|
||||||
|
|||||||
@@ -13,14 +13,20 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
return Regex.Match(color, Pattern).Success;
|
return Regex.Match(color, Pattern).Success;
|
||||||
}
|
}
|
||||||
public string TextColor(string bgColor)
|
|
||||||
|
public Color TextColor(Color bg)
|
||||||
{
|
{
|
||||||
int nThreshold = 105;
|
int nThreshold = 105;
|
||||||
var bg = ColorTranslator.FromHtml(bgColor);
|
int bgDelta = Convert.ToInt32(bg.R * 0.299 + bg.G * 0.587 + bg.B * 0.114);
|
||||||
int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + (bg.B * 0.114));
|
return 255 - bgDelta < nThreshold ? Color.Black : Color.White;
|
||||||
Color color = (255 - bgDelta < nThreshold) ? Color.Black : Color.White;
|
}
|
||||||
|
|
||||||
|
public string TextColor(string bg)
|
||||||
|
{
|
||||||
|
var color = TextColor(FromHtml(bg));
|
||||||
return ColorTranslator.ToHtml(color).ToLowerInvariant();
|
return ColorTranslator.ToHtml(color).ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||||
public static readonly ColorPalette Default = new ColorPalette(new string[] {
|
public static readonly ColorPalette Default = new ColorPalette(new string[] {
|
||||||
"#fbca04",
|
"#fbca04",
|
||||||
@@ -31,6 +37,7 @@ namespace BTCPayServer
|
|||||||
"#cdcdcd",
|
"#cdcdcd",
|
||||||
"#cc317c",
|
"#cc317c",
|
||||||
});
|
});
|
||||||
|
|
||||||
private ColorPalette(string[] labels)
|
private ColorPalette(string[] labels)
|
||||||
{
|
{
|
||||||
Labels = labels;
|
Labels = labels;
|
||||||
@@ -98,5 +105,10 @@ namespace BTCPayServer
|
|||||||
var color = AdjustBrightness(ColorTranslator.FromHtml(html), correctionFactor);
|
var color = AdjustBrightness(ColorTranslator.FromHtml(html), correctionFactor);
|
||||||
return ColorTranslator.ToHtml(color);
|
return ColorTranslator.ToHtml(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Color FromHtml(string html)
|
||||||
|
{
|
||||||
|
return ColorTranslator.FromHtml(html);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Amount = paymentEntity.PaidAmount.Gross,
|
Amount = paymentEntity.PaidAmount.Gross,
|
||||||
Paid = paymentEntity.InvoicePaidAmount.Net,
|
Paid = paymentEntity.InvoicePaidAmount.Net,
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
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),
|
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, i.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var storeBlob = store.GetStoreBlob();
|
var storeBlob = store.GetStoreBlob();
|
||||||
vm.StoreName = store.StoreName;
|
vm.StoreName = store.StoreName;
|
||||||
|
vm.StoreWebsite = store.StoreWebsite;
|
||||||
vm.BrandColor = storeBlob.BrandColor;
|
vm.BrandColor = storeBlob.BrandColor;
|
||||||
vm.LogoFileId = storeBlob.LogoFileId;
|
vm.LogoFileId = storeBlob.LogoFileId;
|
||||||
vm.CssFileId = storeBlob.CssFileId;
|
vm.CssFileId = storeBlob.CssFileId;
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
|||||||
|
|
||||||
[Display(Name = "Expiration Date")]
|
[Display(Name = "Expiration Date")]
|
||||||
public DateTime? ExpiryDate { get; set; }
|
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; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Store")]
|
[Display(Name = "Store")]
|
||||||
@@ -87,7 +91,8 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
|||||||
|
|
||||||
[Display(Name = "Custom CSS Code")]
|
[Display(Name = "Custom CSS Code")]
|
||||||
public string EmbeddedCSS { get; set; }
|
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 bool AllowCustomPaymentAmounts { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, object> FormResponse { get; set; }
|
public Dictionary<string, object> FormResponse { get; set; }
|
||||||
@@ -151,6 +156,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
|
|||||||
public string CssFileId { get; set; }
|
public string CssFileId { get; set; }
|
||||||
public string BrandColor { get; set; }
|
public string BrandColor { get; set; }
|
||||||
public string StoreName { get; set; }
|
public string StoreName { get; set; }
|
||||||
|
public string StoreWebsite { get; set; }
|
||||||
public string EmbeddedCSS { get; set; }
|
public string EmbeddedCSS { get; set; }
|
||||||
public string CustomCSSLink { get; set; }
|
public string CustomCSSLink { get; set; }
|
||||||
|
|
||||||
@@ -208,6 +214,7 @@ 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 AmountFormatted { get; set; }
|
||||||
public string RateFormatted { get; set; }
|
public string RateFormatted { get; set; }
|
||||||
public decimal Paid { get; set; }
|
public decimal Paid { get; set; }
|
||||||
public string PaidFormatted { get; set; }
|
public string PaidFormatted { get; set; }
|
||||||
|
|||||||
@@ -98,9 +98,13 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||||||
|
|
||||||
[MaxLength(30)]
|
[MaxLength(30)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Memo")]
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Custom CSS URL")]
|
[Display(Name = "Custom CSS URL")]
|
||||||
public string CustomCSSLink { get; set; }
|
public string CustomCSSLink { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Custom CSS Code")]
|
[Display(Name = "Custom CSS Code")]
|
||||||
public string EmbeddedCSS { get; set; }
|
public string EmbeddedCSS { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ namespace BTCPayServer.PaymentRequest
|
|||||||
Amount = paymentEntity.PaidAmount.Gross,
|
Amount = paymentEntity.PaidAmount.Gross,
|
||||||
Paid = paymentEntity.InvoicePaidAmount.Net,
|
Paid = paymentEntity.InvoicePaidAmount.Net,
|
||||||
ReceivedDate = paymentEntity.ReceivedTime.DateTime,
|
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),
|
PaidFormatted = _displayFormatter.Currency(paymentEntity.InvoicePaidAmount.Net, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
RateFormatted = _displayFormatter.Currency(paymentEntity.Rate, blob.Currency, DisplayFormatter.CurrencyFormat.Symbol),
|
||||||
PaymentMethod = paymentMethodId.ToPrettyString(),
|
PaymentMethod = paymentMethodId.ToPrettyString(),
|
||||||
|
|||||||
@@ -10,20 +10,30 @@
|
|||||||
}
|
}
|
||||||
@if (!string.IsNullOrEmpty(Model.BrandColor))
|
@if (!string.IsNullOrEmpty(Model.BrandColor))
|
||||||
{
|
{
|
||||||
var brandColor = Model.BrandColor;
|
var brand = Model.BrandColor;
|
||||||
var accentColor = ColorPalette.Default.AdjustBrightness(brandColor, (float)-0.15);
|
var brandColor = ColorPalette.Default.FromHtml(brand);
|
||||||
var complement = ColorPalette.Default.TextColor(brandColor).ToLowerInvariant();
|
var brandRgbValues = $"{brandColor.R}, {brandColor.G}, {brandColor.B}";
|
||||||
var complementColor = $"var(--btcpay-{(complement == "black" ? "black" : "white")})";
|
var accent = ColorPalette.Default.AdjustBrightness(brand, (float)-0.15);
|
||||||
|
var complement = ColorPalette.Default.TextColor(brand);
|
||||||
|
var complementVar = $"var(--btcpay-{(complement == "black" ? "black" : "white")})";
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--btcpay-primary: @brandColor;
|
--btcpay-primary: @brand;
|
||||||
--btcpay-primary-shadow: @brandColor;
|
--btcpay-primary-rgb: @brandRgbValues;
|
||||||
--btcpay-primary-bg-hover: @accentColor;
|
--btcpay-primary-shadow: @brand;
|
||||||
--btcpay-primary-bg-active: @accentColor;
|
--btcpay-primary-bg-hover: @accent;
|
||||||
--btcpay-body-link-accent: @accentColor;
|
--btcpay-primary-bg-active: @accent;
|
||||||
--btcpay-primary-text: @complementColor;
|
--btcpay-body-link: @brand;
|
||||||
--btcpay-primary-text-hover: @complementColor;
|
--btcpay-body-link-accent: @accent;
|
||||||
--btcpay-primary-text-active: @complementColor;
|
--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>
|
</style>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,6 +303,7 @@
|
|||||||
<script src="~/vendor/i18next/i18nextHttpBackend.min.js" asp-append-version="true"></script>
|
<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="~/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/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="~/main/utils.js" asp-append-version="true"></script>
|
||||||
<script src="~/checkout-v2/checkout.js" asp-append-version="true"></script>
|
<script src="~/checkout-v2/checkout.js" asp-append-version="true"></script>
|
||||||
@if (Env.CheatMode)
|
@if (Env.CheatMode)
|
||||||
|
|||||||
@@ -108,23 +108,21 @@
|
|||||||
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
<div id="PaymentDetails" class="bg-tile p-3 p-sm-4 rounded">
|
||||||
<h2 class="h4 mb-3">Payment Details</h2>
|
<h2 class="h4 mb-3">Payment Details</h2>
|
||||||
<div class="table-responsive my-0 d-print-none">
|
<div class="table-responsive my-0 d-print-none">
|
||||||
<table class="table table-borderless my-0">
|
<table class="invoice table table-borderless">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="fw-normal text-secondary">Date</th>
|
<th class="fw-normal text-secondary date-col w-125px">Date</th>
|
||||||
<th class="fw-normal text-secondary">Payment</th>
|
<th class="fw-normal text-secondary amount-col">Paid</th>
|
||||||
<th class="fw-normal text-secondary text-end">Paid</th>
|
<th class="fw-normal text-secondary amount-col w-225px">Payment</th>
|
||||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
<tbody>
|
||||||
@foreach (var payment in Model.Payments)
|
@foreach (var payment in Model.Payments)
|
||||||
{
|
{
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">@payment.ReceivedDate.ToBrowserDate()</td>
|
<td class="date-col">@payment.ReceivedDate.ToBrowserDate()</td>
|
||||||
<td class="text-nowrap">@payment.Amount @payment.PaymentMethod</td>
|
<td class="amount-col">@payment.PaidFormatted</td>
|
||||||
<td class="text-end text-nowrap">@payment.PaidFormatted</td>
|
<td class="amount-col">@payment.AmountFormatted @payment.PaymentMethod</td>
|
||||||
<td class="text-end text-nowrap">@payment.RateFormatted</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
@if (!string.IsNullOrEmpty(payment.Destination))
|
@if (!string.IsNullOrEmpty(payment.Destination))
|
||||||
{
|
{
|
||||||
@@ -132,7 +130,7 @@
|
|||||||
<th class="fw-normal text-nowrap text-secondary">
|
<th class="fw-normal text-nowrap text-secondary">
|
||||||
Destination
|
Destination
|
||||||
</th>
|
</th>
|
||||||
<td class="fw-normal" colspan="3">
|
<td class="fw-normal" colspan="2">
|
||||||
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
<vc:truncate-center text="@payment.Destination" classes="truncate-center-id" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -143,20 +141,20 @@
|
|||||||
<th class="fw-normal text-nowrap text-secondary">
|
<th class="fw-normal text-nowrap text-secondary">
|
||||||
Payment Proof
|
Payment Proof
|
||||||
</th>
|
</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" />
|
<vc:truncate-center text="@payment.PaymentProof" link="@payment.Link" classes="truncate-center-id" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
|
||||||
}
|
}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-print-block">
|
<div class="d-none d-print-block">
|
||||||
@foreach (var payment in Model.Payments)
|
@foreach (var payment in Model.Payments)
|
||||||
{
|
{
|
||||||
<div class="mb-4">
|
<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))
|
@if (!string.IsNullOrEmpty(payment.PaymentProof))
|
||||||
{
|
{
|
||||||
<div>Proof: @payment.PaymentProof</div>
|
<div>Proof: @payment.PaymentProof</div>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ else
|
|||||||
@foreach (var payment in Model.Payments)
|
@foreach (var payment in Model.Payments)
|
||||||
{
|
{
|
||||||
<p> </p>
|
<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">Rate: @payment.RateFormatted</p>
|
||||||
<p class="text-center">= @payment.PaidFormatted</p>
|
<p class="text-center">= @payment.PaidFormatted</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton">Save</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="d-inline-flex align-items-center gap-3">
|
<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">
|
<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">
|
<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" />
|
<vc:icon symbol="dots" />
|
||||||
|
|||||||
@@ -36,14 +36,13 @@
|
|||||||
<partial name="LayoutHead" />
|
<partial name="LayoutHead" />
|
||||||
<partial name="LayoutHeadStoreBranding" model="@(Model.BrandColor, Model.CssFileId, Model.CustomCSSLink, Model.EmbeddedCSS)" />
|
<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" />
|
<link href="~/vendor/bootstrap-vue/bootstrap-vue.min.css" asp-append-version="true" rel="stylesheet" />
|
||||||
<script type="text/javascript">
|
<script>var srvModel = @Safe.Json(Model);</script>
|
||||||
var srvModel = @Safe.Json(Model);
|
|
||||||
</script>
|
|
||||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></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/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/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/signalr/signalr.js" asp-append-version="true"></script>
|
||||||
<script src="~/vendor/animejs/anime.min.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/app.js" asp-append-version="true"></script>
|
||||||
<script src="~/payment-request/services/listener.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>
|
<script src="~/modal/btcpay.js" asp-append-version="true"></script>
|
||||||
@@ -51,6 +50,8 @@
|
|||||||
.invoice { margin-top: var(--btcpay-space-s); }
|
.invoice { margin-top: var(--btcpay-space-s); }
|
||||||
.invoice + .invoice { margin-top: var(--btcpay-space-m); }
|
.invoice + .invoice { margin-top: var(--btcpay-space-m); }
|
||||||
.invoice .badge { font-size: var(--btcpay-font-size-s); }
|
.invoice .badge { font-size: var(--btcpay-font-size-s); }
|
||||||
|
#app { --wrap-max-width: 720px; }
|
||||||
|
#InvoiceDescription > :last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
@@media print {
|
@@media print {
|
||||||
@* This is to avoid table header showing up twice: https://github.com/btcpayserver/btcpayserver/issues/4341 *@
|
@* This is to avoid table header showing up twice: https://github.com/btcpayserver/btcpayserver/issues/4341 *@
|
||||||
@@ -59,119 +60,108 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-vh-100">
|
<body class="min-vh-100">
|
||||||
<div id="app" class="d-flex flex-column min-vh-100 pb-l">
|
<div id="app" class="public-page-wrap">
|
||||||
<nav class="btcpay-header navbar sticky-top py-3 py-lg-4 d-print-block">
|
<main class="flex-grow-1">
|
||||||
<div class="container">
|
<div class="d-flex flex-column justify-content-center gap-4">
|
||||||
<div class="row align-items-center" style="width:calc(100% + 30px)">
|
<partial name="_StoreHeader" model="(Model.Title, Model.LogoFileId)" />
|
||||||
<div class="col-12 col-md-8 col-lg-9">
|
<div class="text-center mt-n3">
|
||||||
<div class="row">
|
Invoice from
|
||||||
<div class="col col-12 col-lg-8">
|
@if (!string.IsNullOrEmpty(Model.StoreWebsite))
|
||||||
<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>
|
<a href="@Model.StoreWebsite" target="_blank" rel="noreferrer noopener">@Model.StoreName</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@Model.StoreName
|
||||||
}
|
}
|
||||||
<template v-if="srvModel.isPending && !srvModel.archived && endDiff">
|
|
||||||
<span class="text-muted">Expires in</span> {{endDiff}}
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<partial name="_StatusMessage" />
|
||||||
</div>
|
<section class="tile">
|
||||||
<div class="col-12 pt-3 pb-2 col-md-4 py-md-0 col-lg-3">
|
<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)
|
||||||
|
{
|
||||||
|
@Model.AmountDueFormatted
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@Model.AmountCollectedFormatted
|
||||||
|
}
|
||||||
|
</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>
|
<noscript>
|
||||||
@if (Model.IsPending && !Model.Archived)
|
<span class="badge badge-@Model.Status.ToLowerInvariant()" data-test="status" style="font-size:.9rem">
|
||||||
{
|
|
||||||
@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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="h2 text-md-end">
|
|
||||||
<span class="badge badge-@Model.Status.ToLowerInvariant()" data-test="status" style="font-size:.75em">
|
|
||||||
@Model.Status
|
@Model.Status
|
||||||
@if (Model.Archived)
|
@if (Model.Archived)
|
||||||
{
|
{
|
||||||
<span>(archived)</span>
|
<span>(archived)</span>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</noscript>
|
</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">
|
</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
|
Pay Invoice
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="srvModel.isPending && !srvModel.archived">
|
<template v-else-if="srvModel.isPending && !srvModel.archived">
|
||||||
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
|
<template v-if="srvModel.allowCustomPaymentAmounts && !srvModel.anyPendingInvoice">
|
||||||
<form v-on:submit="submitCustomAmountForm" class="d-print-none">
|
<form v-on:submit="submitCustomAmountForm">
|
||||||
<div class="row">
|
<div class="input-group mb-3">
|
||||||
<div class="col col-12 col-sm-6 col-md-12">
|
<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 />
|
||||||
<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>
|
<span class="input-group-text">{{currency}}</span>
|
||||||
</div>
|
</div>
|
||||||
</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 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">
|
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
Pay Invoice
|
Pay Invoice
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<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">
|
<div v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<span>Pay Invoice</span>
|
<span>Pay Invoice</span>
|
||||||
</button>
|
</button>
|
||||||
@if (Model.AllowCustomPaymentAmounts) {
|
@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 v-if="loading" class="spinner-grow spinner-grow-sm me-2" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -180,123 +170,136 @@
|
|||||||
}
|
}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<div class="d-flex flex-column flex-sm-row gap-3 align-items-center justify-content-between">
|
||||||
<div class="h2 text-md-end">
|
<button type="button" class="btn btn-secondary only-for-js w-100" v-on:click="window.print">
|
||||||
<span class="badge" :class="`badge-${srvModel.status.toLowerCase()}`" data-test="status" style="font-size:.75em">
|
Print
|
||||||
{{srvModel.status}}
|
</button>
|
||||||
<span v-if="srvModel.archived">(archived)</span>
|
<button type="button" class="btn btn-secondary only-for-js w-100" v-on:click="window.copyUrlToClipboard">
|
||||||
</span>
|
Copy Link
|
||||||
</div>
|
</button>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<noscript>
|
||||||
</nav>
|
@if (Model.IsPending && !Model.Archived)
|
||||||
|
|
||||||
<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>")
|
|
||||||
{
|
{
|
||||||
<div v-html="srvModel.description"></div>
|
@if (Model.AllowCustomPaymentAmounts && !Model.AnyPendingInvoice)
|
||||||
|
{
|
||||||
|
<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
|
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>
|
</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">
|
</template>
|
||||||
<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>
|
<noscript>
|
||||||
@if (Model.Invoices == null || !Model.Invoices.Any())
|
@if (Model.Invoices == null || !Model.Invoices.Any())
|
||||||
{
|
{
|
||||||
<p class="text-muted mt-3 mb-0">No payments made yet.</p>
|
<p class="text-muted mb-0">No payments have been made yet.</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@foreach (var invoice in Model.Invoices)
|
@foreach (var invoice in Model.Invoices)
|
||||||
{
|
{
|
||||||
<div class="table-responsive">
|
<div class="table-responsive my-0">
|
||||||
<table class="invoice table">
|
<table class="invoice table table-borderless">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="table-borderless">
|
<tr>
|
||||||
<th class="fw-normal text-secondary w-350px" scope="col">Invoice Id</th>
|
<th class="fw-normal text-secondary" scope="col">Invoice Id</th>
|
||||||
<th class="fw-normal text-secondary w-175px">Expiry</th>
|
<th class="fw-normal text-secondary amount-col w-125px">Amount</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>
|
<th class="fw-normal text-secondary text-end">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="table-borderless">
|
<tr>
|
||||||
<td>@invoice.Id</td>
|
<td><vc:truncate-center text="@invoice.Id" padding="7" classes="truncate-center-id" /></td>
|
||||||
<td>@invoice.ExpiryDate.ToString("g")</td>
|
<td class="amount-col">@invoice.AmountFormatted</td>
|
||||||
<td class="text-end">@invoice.AmountFormatted</td>
|
|
||||||
<td class="text-end"></td>
|
|
||||||
<td class="text-end text-print-default">
|
<td class="text-end text-print-default">
|
||||||
<span class="badge badge-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
<span class="badge badge-@StatusClass(invoice.State)">@invoice.StateFormatted</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@if (invoice.Payments != null && invoice.Payments.Any())
|
@if (invoice.Payments != null && invoice.Payments.Any())
|
||||||
{
|
{
|
||||||
<tr class="table-borderless">
|
<tr>
|
||||||
<th class="fw-normal text-secondary">Destination</th>
|
<th class="fw-normal text-secondary">Transaction</th>
|
||||||
<th class="fw-normal text-secondary">Received</th>
|
<th class="fw-normal text-secondary amount-col">Paid</th>
|
||||||
<th class="fw-normal text-secondary text-end">Paid</th>
|
|
||||||
<th class="fw-normal text-secondary text-end">Rate</th>
|
|
||||||
<th class="fw-normal text-secondary text-end">Payment</th>
|
<th class="fw-normal text-secondary text-end">Payment</th>
|
||||||
</tr>
|
</tr>
|
||||||
@foreach (var payment in invoice.Payments)
|
@foreach (var payment in invoice.Payments)
|
||||||
{
|
{
|
||||||
<tr class="table-borderless">
|
<tr>
|
||||||
<td class="text-break"><code>@payment.Destination</code></td>
|
<td class="text-break"><vc:truncate-center text="@payment.Id" link="@payment.Link" padding="7" classes="truncate-center-id" /></td>
|
||||||
<td>@payment.ReceivedDate.ToString("g")</td>
|
<td class="amount-col">@payment.PaidFormatted</td>
|
||||||
<td class="text-end">@payment.PaidFormatted</td>
|
<td class="text-end text-nowrap">@payment.AmountFormatted @payment.PaymentMethod</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>
|
</tr>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,67 +309,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</noscript>
|
</noscript>
|
||||||
<template v-if="!srvModel.invoices || srvModel.invoices.length == 0">
|
</section>
|
||||||
<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">
|
|
||||||
<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 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="`badge-${statusClass(invoice.stateFormatted)}`">{{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>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer class="store-footer">
|
<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">
|
<a asp-controller="UIPaymentRequest" asp-action="EditPaymentRequest" asp-route-storeId="@Model.StoreId" asp-route-payReqId="@Model.Id">
|
||||||
Edit payment request
|
Edit payment request
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton">Save</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Automatically Approved</th>
|
<th scope="col">Automatically Approved</th>
|
||||||
<th scope="col">Refunded</th>
|
<th scope="col">Refunded</th>
|
||||||
<th scope="col"></th>
|
<th scope="col" class="actions-col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -146,11 +146,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="actions-col">
|
||||||
<div class="d-inline-flex align-items-center gap-3">
|
<div class="d-inline-flex align-items-center gap-3">
|
||||||
<a asp-action="ViewPullPayment"
|
<a asp-action="ViewPullPayment"
|
||||||
asp-controller="UIPullPayment"
|
asp-controller="UIPullPayment"
|
||||||
asp-route-pullPaymentId="@pp.Id">
|
asp-route-pullPaymentId="@pp.Id"
|
||||||
|
target="_blank">
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
<a class="pp-payout"
|
<a class="pp-payout"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
:root {
|
#Checkout-v2 {
|
||||||
--navbutton-size: .8rem;
|
--navbutton-size: .8rem;
|
||||||
--section-padding: 1.5rem;
|
--section-padding: 1.5rem;
|
||||||
--border-radius: var(--btcpay-border-radius-l);
|
--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
|
// These are the legacy states, see InvoiceEntity
|
||||||
const STATUS_PAYABLE = ['new'];
|
const STATUS_PAYABLE = ['new'];
|
||||||
const STATUS_PAID = ['paid'];
|
const STATUS_PAID = ['paid'];
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
function confirmCopy(el, message) {
|
function confirmCopy(el, message) {
|
||||||
const hasIcon = !!el.innerHTML.match('icon-copy')
|
const hasIcon = !!el.innerHTML.match('icon-copy')
|
||||||
|
const confirmHTML = `<span class="text-success">${message}</span>`;
|
||||||
if (hasIcon) {
|
if (hasIcon) {
|
||||||
el.innerHTML = el.innerHTML.replace('#copy', '#checkmark');
|
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;
|
el.dataset.clipboardConfirming = true;
|
||||||
if (el.dataset.clipboardHandler) {
|
if (el.dataset.clipboardHandler) {
|
||||||
@@ -10,6 +15,8 @@ function confirmCopy(el, message) {
|
|||||||
const timeoutId = setTimeout(function () {
|
const timeoutId = setTimeout(function () {
|
||||||
if (hasIcon) {
|
if (hasIcon) {
|
||||||
el.innerHTML = el.innerHTML.replace('#checkmark', '#copy');
|
el.innerHTML = el.innerHTML.replace('#checkmark', '#copy');
|
||||||
|
} else if (el.innerHTML === confirmHTML) {
|
||||||
|
el.innerHTML = el.dataset.clipboardInitial;
|
||||||
}
|
}
|
||||||
delete el.dataset.clipboardConfirming;
|
delete el.dataset.clipboardConfirming;
|
||||||
el.dataset.clipboardHandler = null;
|
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 */
|
/* Print */
|
||||||
@media print {
|
@media print {
|
||||||
section {
|
section,
|
||||||
|
.tile {
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
}
|
}
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
@@ -245,17 +246,21 @@ h2 svg.icon.icon-info {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
.buttons,
|
||||||
.toasted-container {
|
.toasted-container {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.truncate-center a,
|
.truncate-center a,
|
||||||
.truncate-center button,
|
.truncate-center button,
|
||||||
.truncate-center-truncated {
|
.truncate-center-truncated {
|
||||||
display: none;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.card {
|
.card {
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
}
|
}
|
||||||
|
.actions-col {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Richtext editor */
|
/* Richtext editor */
|
||||||
@@ -638,28 +643,56 @@ input:checked + label.btcpay-list-select-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Public pages */
|
/* Public pages */
|
||||||
.public-page-wrap {
|
@media screen {
|
||||||
|
.public-page-wrap {
|
||||||
|
--wrap-max-width: none;
|
||||||
|
--wrap-padding-vertical: var(--btcpay-space-l);
|
||||||
|
--wrap-padding-horizontal: var(--btcpay-space-m);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
max-width: var(--wrap-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: var(--btcpay-space-l) var(--btcpay-space-m);
|
padding: var(--wrap-padding-vertical) var(--wrap-padding-horizontal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* gradually try to set better but less supported values and units */
|
/* gradually try to set better but less supported values and units */
|
||||||
.min-vh-100,
|
.min-vh-100,
|
||||||
.public-page-wrap {
|
.public-page-wrap {
|
||||||
min-height: -webkit-fill-available !important;
|
min-height: -webkit-fill-available !important;
|
||||||
min-height: 100dvh !important;
|
min-height: 100dvh !important;
|
||||||
}
|
}
|
||||||
|
.tile {
|
||||||
|
--section-padding: 1.5rem;
|
||||||
|
--section-border-radius: var(--btcpay-border-radius-l);
|
||||||
|
|
||||||
@media (max-width: 400px) {
|
padding: var(--section-padding);
|
||||||
.public-page-wrap {
|
background: var(--btcpay-bg-tile);
|
||||||
padding-left: 0;
|
border-radius: var(--section-border-radius);
|
||||||
padding-right: 0;
|
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 {
|
.store-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -705,6 +738,7 @@ a.store-powered-by svg {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
|
.store-footer a:hover,
|
||||||
a.store-powered-by:hover {
|
a.store-powered-by:hover {
|
||||||
color: var(--btcpay-body-text-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;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.truncate-center-end,
|
||||||
|
.truncate-center-start {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.truncate-center-id {
|
.truncate-center-id {
|
||||||
font-family: var(--btcpay-font-monospace);
|
font-family: var(--btcpay-font-monospace);
|
||||||
font-size: .875em;
|
font-size: .875em;
|
||||||
@@ -1064,6 +1103,9 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.actions-col {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mass Actions */
|
/* Mass Actions */
|
||||||
.mass-action-head,
|
.mass-action-head,
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||||||
active: true,
|
active: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
timeoutState: "",
|
timeoutState: "",
|
||||||
customAmount: null
|
customAmount: null,
|
||||||
|
detailsShown: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -109,6 +110,15 @@ document.addEventListener("DOMContentLoaded",function (ev) {
|
|||||||
default:
|
default:
|
||||||
return status.toLowerCase();
|
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 () {
|
mounted: function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user