diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 262610ec8..b7a6f59eb 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -2161,6 +2161,70 @@ namespace BTCPayServer.Tests Assert.Contains("1 222,21 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text); } + [Fact] + [Trait("Selenium", "Selenium")] + [Trait("Lightning", "Lightning")] + public async Task CanUsePOSCart() + { + using var s = CreateSeleniumTester(); + s.Server.ActivateLightning(); + await s.StartAsync(); + + await s.Server.EnsureChannelsSetup(); + + s.RegisterNewUser(true); + s.CreateNewStore(); + s.GoToStore(); + s.AddLightningNode(LightningConnectionType.CLightning, false); + s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click(); + s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString()); + s.Driver.FindElement(By.Id("Create")).Click(); + Assert.Contains("App successfully created", s.FindAlertMessage().Text); + s.Driver.FindElement(By.CssSelector("label[for='DefaultView_Cart']")).Click(); + s.Driver.FindElement(By.Id("Currency")).SendKeys("EUR"); + s.Driver.FindElement(By.Id("ShowCustomAmount")).Click(); + s.Driver.FindElement(By.Id("SaveSettings")).Click(); + 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.WaitForElement(By.Id("js-cart-list")); + Assert.Empty(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr"))); + Assert.Equal("0,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text); + Assert.False(s.Driver.FindElement(By.Id("CartClear")).Displayed); + + // Select and clear + s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(1)")).Click(); + Assert.Single(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr"))); + s.Driver.FindElement(By.Id("CartClear")).Click(); + Assert.Empty(s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr"))); + Thread.Sleep(250); + + // Select items + s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(2)")).Click(); + Thread.Sleep(250); + s.Driver.FindElement(By.CssSelector(".card.js-add-cart:nth-child(1)")).Click(); + Thread.Sleep(250); + Assert.Equal(2, s.Driver.FindElements(By.CssSelector("#js-cart-list tbody tr")).Count); + Assert.Equal("2,00 €", s.Driver.FindElement(By.Id("CartTotal")).Text); + + // Custom amount + s.Driver.FindElement(By.Id("CartCustomAmount")).SendKeys("1.5"); + s.Driver.FindElement(By.Id("CartTotal")).Click(); + Assert.Equal("3,50 €", s.Driver.FindElement(By.Id("CartTotal")).Text); + s.Driver.FindElement(By.Id("js-cart-confirm")).Click(); + + // Pay + Assert.Equal("3,50 €", s.Driver.FindElement(By.Id("CartSummaryTotal")).Text); + s.Driver.FindElement(By.Id("js-cart-pay")).Click(); + + s.Driver.WaitUntilAvailable(By.Id("Checkout-v2")); + s.Driver.FindElement(By.Id("DetailsToggle")).Click(); + s.Driver.WaitForElement(By.Id("PaymentDetails-TotalFiat")); + Assert.Contains("3,50 €", s.Driver.FindElement(By.Id("PaymentDetails-TotalFiat")).Text); + } + [Fact] [Trait("Selenium", "Selenium")] [Trait("Lightning", "Lightning")] diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs index edda12647..2503f98bf 100644 --- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs +++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs @@ -133,6 +133,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount = null, [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? tip = null, [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? discount = null, + [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? customAmount = null, string email = null, string orderId = null, string notificationUrl = null, @@ -232,9 +233,11 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers price += expectedCartItemPrice * cartItem.Value; } - if (discount is decimal d) + if (customAmount is { } c) + price += c; + if (discount is { } d) price -= price * d/100.0m; - if (tip is decimal t) + if (tip is { } t) price += t; } } diff --git a/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml index 349b5ef77..cbe7b28a3 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml @@ -51,17 +51,15 @@ {price} - - - - - @@ -162,7 +157,7 @@ Total products - + @@ -170,8 +165,8 @@ { Discount - - + + } @@ -179,15 +174,15 @@ { Tip - - + + } Total - + @@ -202,10 +197,11 @@ asp-antiforgery="false" data-buy > - - - - + + + + + @@ -318,7 +314,7 @@ - + diff --git a/BTCPayServer/wwwroot/cart/js/cart.js b/BTCPayServer/wwwroot/cart/js/cart.js index c33deb3ea..34e7fe0e2 100644 --- a/BTCPayServer/wwwroot/cart/js/cart.js +++ b/BTCPayServer/wwwroot/cart/js/cart.js @@ -330,9 +330,9 @@ Cart.prototype.updateAmount = function() { $('#js-cart-amount').val(this.getTotal(true)); $('#js-cart-tip').val(this.tip); $('#js-cart-discount').val(this.discount); + $('#js-cart-custom-amount').val(this.customAmount); } Cart.prototype.updatePosData = function() { - var result = { cart: this.content, customAmount: this.fromCents(this.getCustomAmount()),