From 701ba59bd8d9b068912e136fff57350af207f6f0 Mon Sep 17 00:00:00 2001 From: Dennis Reimann Date: Fri, 22 Jul 2022 15:41:14 +0200 Subject: [PATCH] Convert public app parts --- .../AltcoinTests/AltcoinTests.cs | 4 +- BTCPayServer.Tests/CrowdfundTests.cs | 11 +- BTCPayServer.Tests/POSTests.cs | 2 +- BTCPayServer/Controllers/UIAppsController.cs | 22 +- .../Controllers/UIAppsPublicController.cs | 430 ------------------ .../Extensions/UrlHelperExtensions.cs | 4 +- BTCPayServer/Hosting/BTCPayServerServices.cs | 1 - .../Controllers/UICrowdfundController.cs | 193 +++++++- .../Controllers/UIPointOfSaleController.cs | 219 ++++++++- BTCPayServer/Services/Apps/AppHub.cs | 21 +- .../Crowdfund/Public}/ContributeForm.cshtml | 0 .../Crowdfund/Public}/ViewCrowdfund.cshtml | 2 +- .../Shared/Crowdfund/UpdateCrowdfund.cshtml | 2 +- .../PointOfSale/Public}/Cart.cshtml | 6 +- .../Shared/PointOfSale/Public/Light.cshtml | 18 + .../PointOfSale/Public}/MinimalLight.cshtml | 2 +- .../PointOfSale/Public}/Print.cshtml | 3 +- .../PointOfSale/Public}/Static.cshtml | 20 +- .../PointOfSale/Public}/VueLight.cshtml | 2 +- .../Public}/_LayoutPos.cshtml | 0 .../PointOfSale/UpdatePointOfSale.cshtml | 4 +- BTCPayServer/Views/UIApps/ListApps.cshtml | 4 - .../UIAppsPublic/PointOfSale/Light.cshtml | 18 - 23 files changed, 477 insertions(+), 511 deletions(-) delete mode 100644 BTCPayServer/Controllers/UIAppsPublicController.cs rename BTCPayServer/Views/{UIAppsPublic/Crowdfund => Shared/Crowdfund/Public}/ContributeForm.cshtml (100%) rename BTCPayServer/Views/{UIAppsPublic => Shared/Crowdfund/Public}/ViewCrowdfund.cshtml (99%) rename BTCPayServer/Views/{UIAppsPublic/PointOfSale => Shared/PointOfSale/Public}/Cart.cshtml (98%) create mode 100644 BTCPayServer/Views/Shared/PointOfSale/Public/Light.cshtml rename BTCPayServer/Views/{UIAppsPublic/PointOfSale => Shared/PointOfSale/Public}/MinimalLight.cshtml (95%) rename BTCPayServer/Views/{UIAppsPublic/PointOfSale => Shared/PointOfSale/Public}/Print.cshtml (98%) rename BTCPayServer/Views/{UIAppsPublic/PointOfSale => Shared/PointOfSale/Public}/Static.cshtml (86%) rename BTCPayServer/Views/{UIAppsPublic/PointOfSale => Shared/PointOfSale/Public}/VueLight.cshtml (97%) rename BTCPayServer/Views/Shared/{ => PointOfSale/Public}/_LayoutPos.cshtml (100%) delete mode 100644 BTCPayServer/Views/UIAppsPublic/PointOfSale/Light.cshtml diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs index c95890567..7deac859b 100644 --- a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -651,7 +651,7 @@ donation: vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync(); Assert.Equal("hello", vmpos.Title); - var publicApps = user.GetController(); + var publicApps = user.GetController(); var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync(); Assert.Equal("hello", vmview.Title); Assert.Equal(3, vmview.Items.Length); @@ -720,7 +720,7 @@ donation: custom: true "; Assert.IsType(pos.UpdatePointOfSale(app.Id, vmpos).Result); - publicApps = user.GetController(); + publicApps = user.GetController(); vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync(); Assert.Equal(test.Code, vmview.CurrencyCode); Assert.Equal(test.ExpectedSymbol, diff --git a/BTCPayServer.Tests/CrowdfundTests.cs b/BTCPayServer.Tests/CrowdfundTests.cs index e8904ec2c..0e72b6086 100644 --- a/BTCPayServer.Tests/CrowdfundTests.cs +++ b/BTCPayServer.Tests/CrowdfundTests.cs @@ -97,8 +97,8 @@ namespace BTCPayServer.Tests Assert.IsType(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); - var anonAppPubsController = tester.PayTester.GetController(); - var publicApps = user.GetController(); + var anonAppPubsController = tester.PayTester.GetController(); + var crowdfundController = user.GetController(); Assert.IsType(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() { @@ -108,12 +108,12 @@ namespace BTCPayServer.Tests Assert.IsType(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty)); //Scenario 2: Not Enabled But Admin - Allowed - Assert.IsType(await publicApps.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() + Assert.IsType(await crowdfundController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund() { RedirectToCheckout = false, Amount = new decimal(0.01) }, default)); - Assert.IsType(await publicApps.ViewCrowdfund(app.Id, string.Empty)); + Assert.IsType(await crowdfundController.ViewCrowdfund(app.Id, string.Empty)); Assert.IsType(await anonAppPubsController.ViewCrowdfund(app.Id, string.Empty)); //Scenario 3: Enabled But Start Date > Now - Not Allowed @@ -190,8 +190,7 @@ namespace BTCPayServer.Tests crowdfundViewModel.EnforceTargetAmount = true; Assert.IsType(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result); - var anonAppPubsController = tester.PayTester.GetController(); - var publicApps = user.GetController(); + var publicApps = user.GetController(); var model = Assert.IsType(Assert .IsType(publicApps.ViewCrowdfund(app.Id, String.Empty).Result).Model); diff --git a/BTCPayServer.Tests/POSTests.cs b/BTCPayServer.Tests/POSTests.cs index 33f620556..70892b9e6 100644 --- a/BTCPayServer.Tests/POSTests.cs +++ b/BTCPayServer.Tests/POSTests.cs @@ -55,7 +55,7 @@ donation: "; Assert.IsType(pos.UpdatePointOfSale(app.Id, vmpos).Result); await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync(); - var publicApps = user.GetController(); + var publicApps = user.GetController(); var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync(); // apple shouldn't be available since we it's set to "disabled: true" above diff --git a/BTCPayServer/Controllers/UIAppsController.cs b/BTCPayServer/Controllers/UIAppsController.cs index 43bf9c654..7c9641d8e 100644 --- a/BTCPayServer/Controllers/UIAppsController.cs +++ b/BTCPayServer/Controllers/UIAppsController.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers { - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] [Route("apps")] public partial class UIAppsController : Controller @@ -37,7 +36,6 @@ namespace BTCPayServer.Controllers public string CreatedAppId { get; set; } - public class AppUpdated { public string AppId { get; set; } @@ -48,7 +46,23 @@ namespace BTCPayServer.Controllers return string.Empty; } } + + [HttpGet("/apps/{appId}")] + public async Task RedirectToApp(string appId) + { + var app = await _appService.GetApp(appId, null); + if (app is null) + return NotFound(); + + return app.AppType switch + { + nameof(AppType.Crowdfund) => RedirectToAction(nameof(UICrowdfundController.ViewCrowdfund), "UICrowdfund", new { appId }), + nameof(AppType.PointOfSale) => RedirectToAction(nameof(UIPointOfSaleController.ViewPointOfSale), "UIPointOfSale", new { appId }), + _ => NotFound() + }; + } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("/stores/{storeId}/apps")] public async Task ListApps( string storeId, @@ -94,6 +108,7 @@ namespace BTCPayServer.Controllers }); } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("/stores/{storeId}/apps/create")] public IActionResult CreateApp(string storeId) { @@ -103,6 +118,7 @@ namespace BTCPayServer.Controllers }); } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("/stores/{storeId}/apps/create")] public async Task CreateApp(string storeId, CreateAppViewModel vm) { @@ -151,6 +167,7 @@ namespace BTCPayServer.Controllers }; } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/delete")] public IActionResult DeleteApp(string appId) { @@ -161,6 +178,7 @@ namespace BTCPayServer.Controllers return View("Confirm", new ConfirmModel("Delete app", $"The app {app.Name} and its settings will be permanently deleted. Are you sure?", "Delete")); } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("{appId}/delete")] public async Task DeleteAppPost(string appId) { diff --git a/BTCPayServer/Controllers/UIAppsPublicController.cs b/BTCPayServer/Controllers/UIAppsPublicController.cs deleted file mode 100644 index 9a1fd042a..000000000 --- a/BTCPayServer/Controllers/UIAppsPublicController.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Abstractions.Models; -using BTCPayServer.Configuration; -using BTCPayServer.Data; -using BTCPayServer.Filters; -using BTCPayServer.ModelBinders; -using BTCPayServer.Models; -using BTCPayServer.Models.AppViewModels; -using BTCPayServer.Plugins.Crowdfund.Models; -using BTCPayServer.Plugins.PointOfSale.Models; -using BTCPayServer.Services.Apps; -using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using NBitpayClient; -using NicolasDorier.RateLimits; - -namespace BTCPayServer.Controllers -{ - public class UIAppsPublicController : Controller - { - public UIAppsPublicController(AppService appService, - BTCPayServerOptions btcPayServerOptions, - UIInvoiceController invoiceController, - UserManager userManager) - { - _AppService = appService; - _BtcPayServerOptions = btcPayServerOptions; - _InvoiceController = invoiceController; - _UserManager = userManager; - } - - private readonly AppService _AppService; - private readonly BTCPayServerOptions _BtcPayServerOptions; - private readonly UIInvoiceController _InvoiceController; - private readonly UserManager _UserManager; - - [HttpGet("/apps/{appId}")] - public async Task RedirectToApp(string appId) - { - var app = await _AppService.GetApp(appId, null); - if (app is null) - return NotFound(); - switch (app.AppType) - { - case nameof(AppType.Crowdfund): - return RedirectToAction("ViewCrowdfund", new { appId }); - - case nameof(AppType.PointOfSale): - return RedirectToAction("ViewPointOfSale", new { appId }); - } - - return NotFound(); - } - - [HttpGet] - [Route("/")] - [Route("/apps/{appId}/pos/{viewType?}")] - [XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)] - [DomainMappingConstraint(AppType.PointOfSale)] - public async Task ViewPointOfSale(string appId, PosViewType? viewType = null) - { - var app = await _AppService.GetApp(appId, AppType.PointOfSale); - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - var numberFormatInfo = _AppService.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppService.Currencies.GetNumberFormatInfo("USD"); - double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits)); - viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; - var store = await _AppService.GetStore(app); - var storeBlob = store.GetStoreBlob(); - - return View("PointOfSale/" + viewType, new ViewPointOfSaleViewModel() - { - Title = settings.Title, - Step = step.ToString(CultureInfo.InvariantCulture), - ViewType = (PosViewType)viewType, - ShowCustomAmount = settings.ShowCustomAmount, - ShowDiscount = settings.ShowDiscount, - EnableTips = settings.EnableTips, - CurrencyCode = settings.Currency, - CurrencySymbol = numberFormatInfo.CurrencySymbol, - CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData() - { - CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol, - Divisibility = numberFormatInfo.CurrencyDecimalDigits, - DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator, - ThousandSeparator = numberFormatInfo.NumberGroupSeparator, - Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern), - SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern) - }, - Items = _AppService.GetPOSItems(settings.Template, settings.Currency), - ButtonText = settings.ButtonText, - CustomButtonText = settings.CustomButtonText, - CustomTipText = settings.CustomTipText, - CustomTipPercentages = settings.CustomTipPercentages, - CustomCSSLink = settings.CustomCSSLink, - CustomLogoLink = storeBlob.CustomLogo, - AppId = appId, - StoreId = store.Id, - Description = settings.Description, - EmbeddedCSS = settings.EmbeddedCSS, - RequiresRefundEmail = settings.RequiresRefundEmail - }); - } - - [HttpPost] - [Route("/")] - [Route("/apps/{appId}/pos/{viewType?}")] - [XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)] - [IgnoreAntiforgeryToken] - [EnableCors(CorsPolicies.All)] - [DomainMappingConstraint(AppType.PointOfSale)] - [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] - public async Task ViewPointOfSale(string appId, - PosViewType viewType, - [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount, - string email, - string orderId, - string notificationUrl, - string redirectUrl, - string choiceKey, - string posData = null, - RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore, - CancellationToken cancellationToken = default) - { - var app = await _AppService.GetApp(appId, AppType.PointOfSale); - if (string.IsNullOrEmpty(choiceKey) && amount <= 0) - { - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); - } - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; - if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart) - { - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId, viewType = viewType }); - } - string title = null; - decimal? price = null; - Dictionary paymentMethods = null; - ViewPointOfSaleViewModel.Item choice = null; - if (!string.IsNullOrEmpty(choiceKey)) - { - var choices = _AppService.GetPOSItems(settings.Template, settings.Currency); - choice = choices.FirstOrDefault(c => c.Id == choiceKey); - if (choice == null) - return NotFound(); - title = choice.Title; - if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) - { - price = null; - } - else - { - price = choice.Price.Value; - if (amount > price) - price = amount; - } - - - if (choice.Inventory.HasValue) - { - if (choice.Inventory <= 0) - { - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); - } - } - - if (choice?.PaymentMethods?.Any() is true) - { - paymentMethods = choice?.PaymentMethods.ToDictionary(s => s, - s => new InvoiceSupportedTransactionCurrency() { Enabled = true }); - } - } - else - { - if (!settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart) - return NotFound(); - price = amount; - title = settings.Title; - - //if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items - if (!string.IsNullOrEmpty(posData) && - settings.DefaultView == PosViewType.Cart && - AppService.TryParsePosCartItems(posData, out var cartItems)) - { - - var choices = _AppService.GetPOSItems(settings.Template, settings.Currency); - foreach (var cartItem in cartItems) - { - var itemChoice = choices.FirstOrDefault(c => c.Id == cartItem.Key); - if (itemChoice == null) - return NotFound(); - - if (itemChoice.Inventory.HasValue) - { - switch (itemChoice.Inventory) - { - case int i when i <= 0: - return RedirectToAction(nameof(ViewPointOfSale), new { appId }); - case int inventory when inventory < cartItem.Value: - return RedirectToAction(nameof(ViewPointOfSale), new { appId }); - } - } - } - } - } - var store = await _AppService.GetStore(app); - try - { - var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest() - { - ItemCode = choice?.Id, - ItemDesc = title, - Currency = settings.Currency, - Price = price, - BuyerEmail = email, - OrderId = orderId ?? AppService.GetAppOrderId(app), - NotificationURL = - string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl, - RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl - : !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl - : Request.GetDisplayUrl(), - FullNotifications = true, - ExtendedNotifications = true, - PosData = string.IsNullOrEmpty(posData) ? null : posData, - RedirectAutomatically = settings.RedirectAutomatically, - SupportedTransactionCurrencies = paymentMethods, - RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore - ? store.GetStoreBlob().RequiresRefundEmail - : requiresRefundEmail == RequiresRefundEmail.On, - }, store, HttpContext.Request.GetAbsoluteRoot(), - new List() { AppService.GetAppInternalTag(appId) }, - cancellationToken, (entity) => - { - entity.Metadata.OrderUrl = Request.GetDisplayUrl(); - } ); - return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id }); - } - catch (BitpayHttpException e) - { - TempData.SetStatusMessageModel(new StatusMessageModel() - { - Html = e.Message.Replace("\n", "
", StringComparison.OrdinalIgnoreCase), - Severity = StatusMessageModel.StatusSeverity.Error, - AllowDismiss = true - }); - return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); - } - } - - [HttpGet] - [Route("/")] - [Route("/apps/{appId}/crowdfund")] - [XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)] - [DomainMappingConstraintAttribute(AppType.Crowdfund)] - public async Task ViewCrowdfund(string appId, string statusMessage) - { - var app = await _AppService.GetApp(appId, AppType.Crowdfund, true); - - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - - var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null; - - var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency); - if (!hasEnoughSettingsToLoad) - { - if (!isAdmin) - return NotFound(); - - return NotFound("A Target Currency must be set for this app in order to be loadable."); - } - var appInfo = await GetAppInfo(appId); - - if (settings.Enabled) - return View(appInfo); - if (!isAdmin) - return NotFound(); - - return View(appInfo); - } - - [HttpPost] - [Route("/")] - [Route("/apps/{appId}/crowdfund")] - [XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)] - [IgnoreAntiforgeryToken] - [EnableCors(CorsPolicies.All)] - [DomainMappingConstraintAttribute(AppType.Crowdfund)] - [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] - public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) - { - - var app = await _AppService.GetApp(appId, AppType.Crowdfund, true); - - if (app == null) - return NotFound(); - var settings = app.GetSettings(); - - var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null; - - if (!settings.Enabled && !isAdmin) - { - return NotFound("Crowdfund is not currently active"); - } - - var info = await GetAppInfo(appId); - if (!isAdmin && - ((settings.StartDate.HasValue && DateTime.UtcNow < settings.StartDate) || - (settings.EndDate.HasValue && DateTime.UtcNow > settings.EndDate) || - (settings.EnforceTargetAmount && - (info.Info.PendingProgressPercentage.GetValueOrDefault(0) + - info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100))) - { - return NotFound("Crowdfund is not currently active"); - } - - var store = await _AppService.GetStore(app); - var title = settings.Title; - decimal? price = request.Amount; - Dictionary paymentMethods = null; - ViewPointOfSaleViewModel.Item choice = null; - if (!string.IsNullOrEmpty(request.ChoiceKey)) - { - var choices = _AppService.GetPOSItems(settings.PerksTemplate, settings.TargetCurrency); - choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey); - if (choice == null) - return NotFound("Incorrect option provided"); - title = choice.Title; - - if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) - { - price = null; - } - else - { - price = choice.Price.Value; - if (request.Amount > price) - price = request.Amount; - } - if (choice.Inventory.HasValue) - { - if (choice.Inventory <= 0) - { - return NotFound("Option was out of stock"); - } - } - if (choice?.PaymentMethods?.Any() is true) - { - paymentMethods = choice?.PaymentMethods.ToDictionary(s => s, - s => new InvoiceSupportedTransactionCurrency() { Enabled = true }); - } - } - else - { - if (request.Amount < 0) - { - return NotFound("Please provide an amount greater than 0"); - } - - price = request.Amount; - } - - if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price > - (info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount)))) - { - return NotFound("Contribution Amount is more than is currently allowed."); - } - - try - { - var invoice = await _InvoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest() - { - OrderId = AppService.GetAppOrderId(app), - Currency = settings.TargetCurrency, - ItemCode = request.ChoiceKey ?? string.Empty, - ItemDesc = title, - BuyerEmail = request.Email, - Price = price, - NotificationURL = settings.NotificationUrl, - FullNotifications = true, - ExtendedNotifications = true, - SupportedTransactionCurrencies = paymentMethods, - RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(), - }, store, HttpContext.Request.GetAbsoluteRoot(), - new List() {AppService.GetAppInternalTag(appId)}, - cancellationToken, (entity) => - { - entity.Metadata.OrderUrl = Request.GetDisplayUrl(); - }); - - if (request.RedirectToCheckout) - { - return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", - new {invoiceId = invoice.Data.Id}); - } - - return Ok(invoice.Data.Id); - } - catch (BitpayHttpException e) - { - return BadRequest(e.Message); - } - } - - private async Task GetAppInfo(string appId) - { - var info = (ViewCrowdfundViewModel)await _AppService.GetAppInfo(appId); - info.HubPath = AppHub.GetHubPath(Request); - info.SimpleDisplay = Request.Query.ContainsKey("simple"); - return info; - } - - private string GetUserId() - { - return _UserManager.GetUserId(User); - } - } -} diff --git a/BTCPayServer/Extensions/UrlHelperExtensions.cs b/BTCPayServer/Extensions/UrlHelperExtensions.cs index c9e9bec33..b575e60b0 100644 --- a/BTCPayServer/Extensions/UrlHelperExtensions.cs +++ b/BTCPayServer/Extensions/UrlHelperExtensions.cs @@ -40,8 +40,8 @@ namespace Microsoft.AspNetCore.Mvc public static string AppLink(this LinkGenerator urlHelper, string appId, string scheme, HostString host, string pathbase) { return urlHelper.GetUriByAction( - action: nameof(UIAppsPublicController.RedirectToApp), - controller: "UIAppsPublic", + action: nameof(UIAppsController.RedirectToApp), + controller: "UIApps", values: new { appId }, scheme, host, pathbase); } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 5aee66009..132738daf 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -415,7 +415,6 @@ namespace BTCPayServer.Hosting services.TryAddScoped(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); // Add application services. services.AddSingleton(); diff --git a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs index b9556636e..fe27a9a46 100644 --- a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs +++ b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs @@ -1,41 +1,211 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Controllers; using BTCPayServer.Data; +using BTCPayServer.Filters; +using BTCPayServer.Models; using BTCPayServer.Plugins.Crowdfund.Models; +using BTCPayServer.Plugins.PointOfSale.Models; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using NBitpayClient; +using NicolasDorier.RateLimits; namespace BTCPayServer.Plugins.Crowdfund.Controllers { - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] [Route("apps")] public class UICrowdfundController : Controller { public UICrowdfundController( - EventAggregator eventAggregator, + AppService appService, CurrencyNameTable currencies, + EventAggregator eventAggregator, StoreRepository storeRepository, - AppService appService) + UIInvoiceController invoiceController, + UserManager userManager) { - _eventAggregator = eventAggregator; _currencies = currencies; - _storeRepository = storeRepository; _appService = appService; + _userManager = userManager; + _storeRepository = storeRepository; + _eventAggregator = eventAggregator; + _invoiceController = invoiceController; } private readonly EventAggregator _eventAggregator; private readonly CurrencyNameTable _currencies; private readonly StoreRepository _storeRepository; private readonly AppService _appService; + private readonly UIInvoiceController _invoiceController; + private readonly UserManager _userManager; + + [HttpGet("/")] + [HttpGet("/apps/{appId}/crowdfund")] + [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)] + [DomainMappingConstraint(AppType.Crowdfund)] + public async Task ViewCrowdfund(string appId, string statusMessage) + { + var app = await _appService.GetApp(appId, AppType.Crowdfund, true); + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + + var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null; + + var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency); + if (!hasEnoughSettingsToLoad) + { + if (!isAdmin) + return NotFound(); + + return NotFound("A Target Currency must be set for this app in order to be loadable."); + } + var appInfo = await GetAppInfo(appId); + + if (settings.Enabled) + return View("Crowdfund/Public/ViewCrowdfund", appInfo); + if (!isAdmin) + return NotFound(); + + return View("Crowdfund/Public/ViewCrowdfund", appInfo); + } + + [HttpPost("/")] + [HttpPost("/apps/{appId}/crowdfund")] + [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)] + [IgnoreAntiforgeryToken] + [EnableCors(CorsPolicies.All)] + [DomainMappingConstraint(AppType.Crowdfund)] + [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] + public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken) + { + + var app = await _appService.GetApp(appId, AppType.Crowdfund, true); + + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + + var isAdmin = await _appService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null; + + if (!settings.Enabled && !isAdmin) + { + return NotFound("Crowdfund is not currently active"); + } + + var info = await GetAppInfo(appId); + if (!isAdmin && + ((settings.StartDate.HasValue && DateTime.UtcNow < settings.StartDate) || + (settings.EndDate.HasValue && DateTime.UtcNow > settings.EndDate) || + (settings.EnforceTargetAmount && + (info.Info.PendingProgressPercentage.GetValueOrDefault(0) + + info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100))) + { + return NotFound("Crowdfund is not currently active"); + } + + var store = await _appService.GetStore(app); + var title = settings.Title; + decimal? price = request.Amount; + Dictionary paymentMethods = null; + ViewPointOfSaleViewModel.Item choice = null; + if (!string.IsNullOrEmpty(request.ChoiceKey)) + { + var choices = _appService.GetPOSItems(settings.PerksTemplate, settings.TargetCurrency); + choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey); + if (choice == null) + return NotFound("Incorrect option provided"); + title = choice.Title; + + if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) + { + price = null; + } + else + { + price = choice.Price.Value; + if (request.Amount > price) + price = request.Amount; + } + if (choice.Inventory.HasValue) + { + if (choice.Inventory <= 0) + { + return NotFound("Option was out of stock"); + } + } + if (choice?.PaymentMethods?.Any() is true) + { + paymentMethods = choice?.PaymentMethods.ToDictionary(s => s, + s => new InvoiceSupportedTransactionCurrency() { Enabled = true }); + } + } + else + { + if (request.Amount < 0) + { + return NotFound("Please provide an amount greater than 0"); + } + + price = request.Amount; + } + + if (!isAdmin && (settings.EnforceTargetAmount && info.TargetAmount.HasValue && price > + (info.TargetAmount - (info.Info.CurrentAmount + info.Info.CurrentPendingAmount)))) + { + return NotFound("Contribution Amount is more than is currently allowed."); + } + + try + { + var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest() + { + OrderId = AppService.GetAppOrderId(app), + Currency = settings.TargetCurrency, + ItemCode = request.ChoiceKey ?? string.Empty, + ItemDesc = title, + BuyerEmail = request.Email, + Price = price, + NotificationURL = settings.NotificationUrl, + FullNotifications = true, + ExtendedNotifications = true, + SupportedTransactionCurrencies = paymentMethods, + RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl(), + }, store, HttpContext.Request.GetAbsoluteRoot(), + new List() {AppService.GetAppInternalTag(appId)}, + cancellationToken, (entity) => + { + entity.Metadata.OrderUrl = Request.GetDisplayUrl(); + }); + + if (request.RedirectToCheckout) + { + return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", + new {invoiceId = invoice.Data.Id}); + } + + return Ok(invoice.Data.Id); + } + catch (BitpayHttpException e) + { + return BadRequest(e.Message); + } + } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId) @@ -85,6 +255,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers return View("Crowdfund/UpdateCrowdfund", vm); } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command) { @@ -199,7 +370,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers return RedirectToAction(nameof(UpdateCrowdfund), new { appId }); } - async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) + private async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) { if (string.IsNullOrWhiteSpace(currency)) { @@ -209,5 +380,15 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers } private AppData GetCurrentApp() => HttpContext.GetAppData(); + + private string GetUserId() => _userManager.GetUserId(User); + + private async Task GetAppInfo(string appId) + { + var info = (ViewCrowdfundViewModel)await _appService.GetAppInfo(appId); + info.HubPath = AppHub.GetHubPath(Request); + info.SimpleDisplay = Request.Query.ContainsKey("simple"); + return info; + } } } diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs index aec80fb02..be36dbfe0 100644 --- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs +++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs @@ -1,42 +1,252 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; +using BTCPayServer.Controllers; using BTCPayServer.Data; +using BTCPayServer.Filters; +using BTCPayServer.ModelBinders; +using BTCPayServer.Models; using BTCPayServer.Plugins.PointOfSale.Models; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using NBitpayClient; +using NicolasDorier.RateLimits; namespace BTCPayServer.Plugins.PointOfSale.Controllers { - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] [Route("apps")] public class UIPointOfSaleController : Controller { public UIPointOfSaleController( + AppService appService, CurrencyNameTable currencies, StoreRepository storeRepository, - AppService appService) + UIInvoiceController invoiceController) { _currencies = currencies; - _storeRepository = storeRepository; _appService = appService; + _storeRepository = storeRepository; + _invoiceController = invoiceController; } private readonly CurrencyNameTable _currencies; private readonly StoreRepository _storeRepository; private readonly AppService _appService; + private readonly UIInvoiceController _invoiceController; + [HttpGet("/")] + [HttpGet("/apps/{appId}/pos/{viewType?}")] + [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)] + [DomainMappingConstraint(AppType.PointOfSale)] + public async Task ViewPointOfSale(string appId, PosViewType? viewType = null) + { + var app = await _appService.GetApp(appId, AppType.PointOfSale); + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + var numberFormatInfo = _appService.Currencies.GetNumberFormatInfo(settings.Currency) ?? + _appService.Currencies.GetNumberFormatInfo("USD"); + double step = Math.Pow(10, -numberFormatInfo.CurrencyDecimalDigits); + viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; + var store = await _appService.GetStore(app); + var storeBlob = store.GetStoreBlob(); + + return View($"PointOfSale/Public/{viewType}", new ViewPointOfSaleViewModel + { + Title = settings.Title, + Step = step.ToString(CultureInfo.InvariantCulture), + ViewType = (PosViewType)viewType, + ShowCustomAmount = settings.ShowCustomAmount, + ShowDiscount = settings.ShowDiscount, + EnableTips = settings.EnableTips, + CurrencyCode = settings.Currency, + CurrencySymbol = numberFormatInfo.CurrencySymbol, + CurrencyInfo = new ViewPointOfSaleViewModel.CurrencyInfoData + { + CurrencySymbol = string.IsNullOrEmpty(numberFormatInfo.CurrencySymbol) ? settings.Currency : numberFormatInfo.CurrencySymbol, + Divisibility = numberFormatInfo.CurrencyDecimalDigits, + DecimalSeparator = numberFormatInfo.CurrencyDecimalSeparator, + ThousandSeparator = numberFormatInfo.NumberGroupSeparator, + Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern), + SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern) + }, + Items = _appService.GetPOSItems(settings.Template, settings.Currency), + ButtonText = settings.ButtonText, + CustomButtonText = settings.CustomButtonText, + CustomTipText = settings.CustomTipText, + CustomTipPercentages = settings.CustomTipPercentages, + CustomCSSLink = settings.CustomCSSLink, + CustomLogoLink = storeBlob.CustomLogo, + AppId = appId, + StoreId = store.Id, + Description = settings.Description, + EmbeddedCSS = settings.EmbeddedCSS, + RequiresRefundEmail = settings.RequiresRefundEmail + }); + } + + [HttpPost("/")] + [HttpPost("/apps/{appId}/pos/{viewType?}")] + [XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)] + [IgnoreAntiforgeryToken] + [EnableCors(CorsPolicies.All)] + [DomainMappingConstraint(AppType.PointOfSale)] + [RateLimitsFilter(ZoneLimits.PublicInvoices, Scope = RateLimitsScope.RemoteAddress)] + public async Task ViewPointOfSale(string appId, + PosViewType viewType, + [ModelBinder(typeof(InvariantDecimalModelBinder))] decimal? amount, + string email, + string orderId, + string notificationUrl, + string redirectUrl, + string choiceKey, + string posData = null, + RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore, + CancellationToken cancellationToken = default) + { + var app = await _appService.GetApp(appId, AppType.PointOfSale); + if (string.IsNullOrEmpty(choiceKey) && amount <= 0) + { + return RedirectToAction(nameof(ViewPointOfSale), new { appId }); + } + if (app == null) + return NotFound(); + var settings = app.GetSettings(); + settings.DefaultView = settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; + if (string.IsNullOrEmpty(choiceKey) && !settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart) + { + return RedirectToAction(nameof(ViewPointOfSale), new { appId, viewType }); + } + string title = null; + decimal? price = null; + Dictionary paymentMethods = null; + ViewPointOfSaleViewModel.Item choice = null; + if (!string.IsNullOrEmpty(choiceKey)) + { + var choices = _appService.GetPOSItems(settings.Template, settings.Currency); + choice = choices.FirstOrDefault(c => c.Id == choiceKey); + if (choice == null) + return NotFound(); + title = choice.Title; + if (choice.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) + { + price = null; + } + else + { + price = choice.Price.Value; + if (amount > price) + price = amount; + } + + if (choice.Inventory.HasValue) + { + if (choice.Inventory <= 0) + { + return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); + } + } + + if (choice?.PaymentMethods?.Any() is true) + { + paymentMethods = choice?.PaymentMethods.ToDictionary(s => s, + s => new InvoiceSupportedTransactionCurrency() { Enabled = true }); + } + } + else + { + if (!settings.ShowCustomAmount && settings.DefaultView != PosViewType.Cart) + return NotFound(); + price = amount; + title = settings.Title; + + //if cart IS enabled and we detect posdata that matches the cart system's, check inventory for the items + if (!string.IsNullOrEmpty(posData) && + settings.DefaultView == PosViewType.Cart && + AppService.TryParsePosCartItems(posData, out var cartItems)) + { + + var choices = _appService.GetPOSItems(settings.Template, settings.Currency); + foreach (var cartItem in cartItems) + { + var itemChoice = choices.FirstOrDefault(c => c.Id == cartItem.Key); + if (itemChoice == null) + return NotFound(); + + if (itemChoice.Inventory.HasValue) + { + switch (itemChoice.Inventory) + { + case int i when i <= 0: + return RedirectToAction(nameof(ViewPointOfSale), new { appId }); + case int inventory when inventory < cartItem.Value: + return RedirectToAction(nameof(ViewPointOfSale), new { appId }); + } + } + } + } + } + var store = await _appService.GetStore(app); + try + { + var invoice = await _invoiceController.CreateInvoiceCore(new BitpayCreateInvoiceRequest() + { + ItemCode = choice?.Id, + ItemDesc = title, + Currency = settings.Currency, + Price = price, + BuyerEmail = email, + OrderId = orderId ?? AppService.GetAppOrderId(app), + NotificationURL = + string.IsNullOrEmpty(notificationUrl) ? settings.NotificationUrl : notificationUrl, + RedirectURL = !string.IsNullOrEmpty(redirectUrl) ? redirectUrl + : !string.IsNullOrEmpty(settings.RedirectUrl) ? settings.RedirectUrl + : Request.GetDisplayUrl(), + FullNotifications = true, + ExtendedNotifications = true, + PosData = string.IsNullOrEmpty(posData) ? null : posData, + RedirectAutomatically = settings.RedirectAutomatically, + SupportedTransactionCurrencies = paymentMethods, + RequiresRefundEmail = requiresRefundEmail == RequiresRefundEmail.InheritFromStore + ? store.GetStoreBlob().RequiresRefundEmail + : requiresRefundEmail == RequiresRefundEmail.On, + }, store, HttpContext.Request.GetAbsoluteRoot(), + new List() { AppService.GetAppInternalTag(appId) }, + cancellationToken, (entity) => + { + entity.Metadata.OrderUrl = Request.GetDisplayUrl(); + } ); + return RedirectToAction(nameof(UIInvoiceController.Checkout), "UIInvoice", new { invoiceId = invoice.Data.Id }); + } + catch (BitpayHttpException e) + { + TempData.SetStatusMessageModel(new StatusMessageModel + { + Html = e.Message.Replace("\n", "
", StringComparison.OrdinalIgnoreCase), + Severity = StatusMessageModel.StatusSeverity.Error, + AllowDismiss = true + }); + return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId }); + } + } + + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId) { @@ -113,6 +323,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers return View("PointOfSale/UpdatePointOfSale", vm); } + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm) { @@ -180,7 +391,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); } - async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) + private async Task GetStoreDefaultCurrentIfEmpty(string storeId, string currency) { if (string.IsNullOrWhiteSpace(currency)) { diff --git a/BTCPayServer/Services/Apps/AppHub.cs b/BTCPayServer/Services/Apps/AppHub.cs index e2c93afc1..0d9e26f6d 100644 --- a/BTCPayServer/Services/Apps/AppHub.cs +++ b/BTCPayServer/Services/Apps/AppHub.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Controllers; using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Plugins.Crowdfund.Controllers; using BTCPayServer.Plugins.Crowdfund.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -18,12 +19,14 @@ namespace BTCPayServer.Services.Apps public const string PaymentReceived = "PaymentReceived"; public const string InfoUpdated = "InfoUpdated"; public const string InvoiceError = "InvoiceError"; - private readonly UIAppsPublicController _AppsPublicController; + + private readonly UICrowdfundController _crowdfundController; - public AppHub(UIAppsPublicController appsPublicController) + public AppHub(UICrowdfundController crowdfundController) { - _AppsPublicController = appsPublicController; + _crowdfundController = crowdfundController; } + public async Task ListenToCrowdfundApp(string appId) { if (Context.Items.ContainsKey("app")) @@ -35,16 +38,15 @@ namespace BTCPayServer.Services.Apps await Groups.AddToGroupAsync(Context.ConnectionId, appId); } - public async Task CreateInvoice(ContributeToCrowdfund model) { model.RedirectToCheckout = false; - _AppsPublicController.ControllerContext.HttpContext = Context.GetHttpContext(); + _crowdfundController.ControllerContext.HttpContext = Context.GetHttpContext(); try { var result = - await _AppsPublicController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, Context.ConnectionAborted); + await _crowdfundController.ContributeToCrowdfund(Context.Items["app"].ToString(), model, Context.ConnectionAborted); switch (result) { case OkObjectResult okObjectResult: @@ -54,16 +56,14 @@ namespace BTCPayServer.Services.Apps await Clients.Caller.SendCoreAsync(InvoiceError, new[] { objectResult.Value }); break; default: - await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty()); + await Clients.Caller.SendCoreAsync(InvoiceError, Array.Empty()); break; } } catch (Exception) { - await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty()); - + await Clients.Caller.SendCoreAsync(InvoiceError, Array.Empty()); } - } public static string GetHubPath(HttpRequest request) @@ -75,6 +75,5 @@ namespace BTCPayServer.Services.Apps { route.MapHub("/apps/hub"); } - } } diff --git a/BTCPayServer/Views/UIAppsPublic/Crowdfund/ContributeForm.cshtml b/BTCPayServer/Views/Shared/Crowdfund/Public/ContributeForm.cshtml similarity index 100% rename from BTCPayServer/Views/UIAppsPublic/Crowdfund/ContributeForm.cshtml rename to BTCPayServer/Views/Shared/Crowdfund/Public/ContributeForm.cshtml diff --git a/BTCPayServer/Views/UIAppsPublic/ViewCrowdfund.cshtml b/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml similarity index 99% rename from BTCPayServer/Views/UIAppsPublic/ViewCrowdfund.cshtml rename to BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml index fb20662fd..62c115637 100644 --- a/BTCPayServer/Views/UIAppsPublic/ViewCrowdfund.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/Public/ViewCrowdfund.cshtml @@ -252,7 +252,7 @@
diff --git a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml index ef6a55929..3d855e613 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml @@ -31,7 +31,7 @@ @if (Model.ModelWithMinimumData) { - View + View } diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Cart.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml similarity index 98% rename from BTCPayServer/Views/UIAppsPublic/PointOfSale/Cart.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml index 1201220ed..5d44491d6 100644 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Cart.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml @@ -1,9 +1,8 @@ -@using BTCPayServer.Models.AppViewModels @using BTCPayServer.Plugins.PointOfSale.Models @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel @{ - Layout = "_LayoutPos"; - int[] customTipPercentages = Model.CustomTipPercentages; + Layout = "PointOfSale/Public/_LayoutPos"; + var customTipPercentages = Model.CustomTipPercentages; var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue); } @@ -162,7 +161,6 @@
+ +@if (Context.Request.Query.ContainsKey("simple")) +{ + +} +else +{ + + +} diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/MinimalLight.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/MinimalLight.cshtml similarity index 95% rename from BTCPayServer/Views/UIAppsPublic/PointOfSale/MinimalLight.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/MinimalLight.cshtml index 9bfc59a4f..6effd4d43 100644 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/MinimalLight.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/MinimalLight.cshtml @@ -7,7 +7,7 @@ }
- +
@Model.CurrencySymbol diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Print.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/Print.cshtml similarity index 98% rename from BTCPayServer/Views/UIAppsPublic/PointOfSale/Print.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/Print.cshtml index 5d819ddba..9a93a3344 100644 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Print.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/Print.cshtml @@ -1,4 +1,3 @@ -@using BTCPayServer.Models.AppViewModels @using BTCPayServer.Payments.Lightning @using BTCPayServer.Plugins.PointOfSale.Models @using BTCPayServer.Services.Stores @@ -20,7 +19,7 @@ @{ var store = await StoreRepository.FindStore(Model.StoreId); - Layout = "_LayoutPos"; + Layout = "PointOfSale/Public/_LayoutPos"; Context.Request.Query.TryGetValue("cryptocode", out var cryptoCodeValues); var cryptoCode = cryptoCodeValues.FirstOrDefault() ?? "BTC"; var supported = store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType().OrderBy(method => method.CryptoCode == cryptoCode).FirstOrDefault(); diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Static.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/Static.cshtml similarity index 86% rename from BTCPayServer/Views/UIAppsPublic/PointOfSale/Static.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/Static.cshtml index 5077b8a11..569b3aabc 100644 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Static.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/Static.cshtml @@ -1,8 +1,7 @@ -@using BTCPayServer.Models.AppViewModels @using BTCPayServer.Plugins.PointOfSale.Models @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel @{ - Layout = "_LayoutPos"; + Layout = "PointOfSale/Public/_LayoutPos"; var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue); } @@ -18,15 +17,14 @@
}
- @for (int x = 0; x < Model.Items.Length; x++) + @for (var x = 0; x < Model.Items.Length; x++) { var item = Model.Items[x]; var buttonText = string.IsNullOrEmpty(item.BuyButtonText) ? item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed ? Model.CustomButtonText : Model.ButtonText : item.BuyButtonText; - buttonText = buttonText.Replace("{0}",item.Price.Formatted) - ?.Replace("{Price}",item.Price.Formatted); + buttonText = buttonText.Replace("{0}",item.Price.Formatted).Replace("{Price}",item.Price.Formatted);
- @if (!String.IsNullOrWhiteSpace(item.Image)) + @if (!string.IsNullOrWhiteSpace(item.Image)) { Card image cap } @@ -36,7 +34,7 @@ { @if (item.Price.Type != ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Topup) { - + @{PayFormInputContent(item.BuyButtonText ?? Model.CustomButtonText, item.Price.Type, item.Price.Value, item.Price.Value);} @@ -44,7 +42,7 @@ } else { - +
} - } private void CardBody(string title, string description) {
@title
- @if (!String.IsNullOrWhiteSpace(description)) + @if (!string.IsNullOrWhiteSpace(description)) {

@Safe.Raw(description)

} diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/VueLight.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml similarity index 97% rename from BTCPayServer/Views/UIAppsPublic/PointOfSale/VueLight.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml index 41aff156f..ee5f96191 100644 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/VueLight.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/Public/VueLight.cshtml @@ -1,6 +1,6 @@ @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
-
+
@if (!string.IsNullOrEmpty(Model.CustomLogoLink)) { diff --git a/BTCPayServer/Views/Shared/_LayoutPos.cshtml b/BTCPayServer/Views/Shared/PointOfSale/Public/_LayoutPos.cshtml similarity index 100% rename from BTCPayServer/Views/Shared/_LayoutPos.cshtml rename to BTCPayServer/Views/Shared/PointOfSale/Public/_LayoutPos.cshtml diff --git a/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml index 70bd90045..4d3361022 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/UpdatePointOfSale.cshtml @@ -13,7 +13,7 @@

@ViewData["Title"]

- View + View
@@ -173,7 +173,7 @@
You can embed this POS via an iframe. @{ - var iframe = $""; + var iframe = $""; }
@iframe
diff --git a/BTCPayServer/Views/UIApps/ListApps.cshtml b/BTCPayServer/Views/UIApps/ListApps.cshtml index 62f9cb614..96b1a52b4 100644 --- a/BTCPayServer/Views/UIApps/ListApps.cshtml +++ b/BTCPayServer/Views/UIApps/ListApps.cshtml @@ -103,10 +103,6 @@ Settings - } - View - - - Delete diff --git a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Light.cshtml b/BTCPayServer/Views/UIAppsPublic/PointOfSale/Light.cshtml deleted file mode 100644 index 2686d1722..000000000 --- a/BTCPayServer/Views/UIAppsPublic/PointOfSale/Light.cshtml +++ /dev/null @@ -1,18 +0,0 @@ -@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel -@{ - Layout = "_LayoutPos"; -} - - - -@if (Context.Request.Query.ContainsKey("simple")) -{ - -} -else -{ - - -}