diff --git a/BTCPayServer/Controllers/AppsController.Crowdsale.cs b/BTCPayServer/Controllers/AppsController.Crowdsale.cs index cc9a35123..8e95443f7 100644 --- a/BTCPayServer/Controllers/AppsController.Crowdsale.cs +++ b/BTCPayServer/Controllers/AppsController.Crowdsale.cs @@ -22,6 +22,9 @@ namespace BTCPayServer.Controllers public bool EnforceTargetAmount { get; set; } public string CustomCSSLink { get; set; } + public string MainImageUrl { get; set; } + public string NotificationUrl { get; set; } + public string Tagline { get; set; } } @@ -41,9 +44,12 @@ namespace BTCPayServer.Controllers StartDate = settings.StartDate, TargetCurrency = settings.TargetCurrency, Description = settings.Description, + MainImageUrl = settings.MainImageUrl, EndDate = settings.EndDate, TargetAmount = settings.TargetAmount, - CustomCSSLink = settings.CustomCSSLink + CustomCSSLink = settings.CustomCSSLink, + NotificationUrl = settings.NotificationUrl, + Tagline = settings.Tagline }; return View(vm); } @@ -62,12 +68,15 @@ namespace BTCPayServer.Controllers Title = vm.Title, Enabled = vm.Enabled, EnforceTargetAmount = vm.EnforceTargetAmount, - StartDate = vm.StartDate, + StartDate = vm.StartDate?.ToUniversalTime(), TargetCurrency = vm.TargetCurrency, Description = vm.Description, - EndDate = vm.EndDate, + EndDate = vm.EndDate?.ToUniversalTime(), TargetAmount = vm.TargetAmount, - CustomCSSLink = vm.CustomCSSLink + CustomCSSLink = vm.CustomCSSLink, + MainImageUrl = vm.MainImageUrl, + NotificationUrl = vm.NotificationUrl, + Tagline = vm.Tagline }); await UpdateAppSettings(app); StatusMessage = "App updated"; diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index 9df01f28d..7235e731a 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -86,37 +86,46 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("/apps/{appId}/crowdfund")] [XFrameOptionsAttribute(null)] - public async Task ViewCrowdfund(string appId) + public async Task ViewCrowdfund(string appId, string statusMessage) + { - var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund); + var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true); if (app == null) return NotFound(); var settings = app.GetSettings(); var currency = _AppsHelper.GetCurrencyData(settings.TargetCurrency, false); - return View(CrowdfundHelper.GetInfo(app, _invoiceRepository, _rateFetcher, _btcPayNetworkProvider )); + return View(await CrowdfundHelper.GetInfo(app, _invoiceRepository, _rateFetcher, _btcPayNetworkProvider, statusMessage )); } - + [HttpPost] - [Route("/apps/{appId}/crowdfund/contribute")] + [Route("/apps/{appId}/crowdfund")] [XFrameOptionsAttribute(null)] [IgnoreAntiforgeryToken] [EnableCors(CorsPolicies.All)] - public async Task ContributeToCrowdfund(string appId,[FromBody]ContributeToCrowdfund request, [FromQuery]bool redirectToCheckout) + public async Task ContributeToCrowdfund(string appId, ContributeToCrowdfund request) { - var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund); + var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true); if (app == null) return NotFound(); var settings = app.GetSettings(); - var currency = _AppsHelper.GetCurrencyData(settings.TargetCurrency, false); var store = await _AppsHelper.GetStore(app); + + store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice() { - + OrderId = appId, + Currency = settings.TargetCurrency, + BuyerEmail = request.Email, + Price = request.Amount, + NotificationURL = settings.NotificationUrl, + FullNotifications = true, + ExtendedNotifications = true, }, store, HttpContext.Request.GetAbsoluteRoot()); - if (redirectToCheckout) + if (request.RedirectToCheckout) { - return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id }); + return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", + new {invoiceId = invoice.Data.Id}); } else { @@ -208,9 +217,10 @@ namespace BTCPayServer.Controllers var finalTasks = new List(); foreach (var rateTask in ratesTask) { - finalTasks.Add(rateTask.Value.ContinueWith(task => + finalTasks.Add(Task.Run(async () => { - var rate = task.Result.BidAsk?.Bid; + var tResult = await rateTask.Value; + var rate = tResult.BidAsk?.Bid; if (rate == null) return; var currencyGroup = groupingByCurrency.Single(entities => entities.Key == rateTask.Key.Left); result += currencyGroup.Sum(entity => entity.ProductInformation.Price / rate.Value); @@ -224,7 +234,7 @@ namespace BTCPayServer.Controllers } public static async Task GetInfo(AppData appData, InvoiceRepository invoiceRepository, - RateFetcher rateFetcher, BTCPayNetworkProvider btcPayNetworkProvider) + RateFetcher rateFetcher, BTCPayNetworkProvider btcPayNetworkProvider, string statusMessage= null) { var settings = appData.GetSettings(); var invoices = await GetPaidInvoicesForApp(appData, invoiceRepository); @@ -233,15 +243,17 @@ namespace BTCPayServer.Controllers invoices, settings.TargetCurrency, rateFetcher, rateRules); var paidInvoices = invoices.Length; - var active = (settings.StartDate == null || DateTime.UtcNow >= settings.StartDate) && - (settings.EndDate == null || DateTime.UtcNow <= settings.EndDate) && - (!settings.EnforceTargetAmount || settings.TargetAmount > currentAmount) + var active = (settings.StartDate == null || DateTime.UtcNow >= settings.StartDate) && + (settings.EndDate == null || DateTime.UtcNow <= settings.EndDate) && + (!settings.EnforceTargetAmount || settings.TargetAmount > currentAmount); return new ViewCrowdfundViewModel() { Title = settings.Title, + Tagline = settings.Tagline, Description = settings.Description, CustomCSSLink = settings.CustomCSSLink, + MainImageUrl = settings.MainImageUrl, StoreId = appData.StoreDataId, AppId = appData.Id, StartDate = settings.StartDate, @@ -249,11 +261,16 @@ namespace BTCPayServer.Controllers TargetAmount = settings.TargetAmount, TargetCurrency = settings.TargetCurrency, EnforceTargetAmount = settings.EnforceTargetAmount, + StatusMessage = statusMessage, Info = new ViewCrowdfundViewModel.CrowdfundInfo() { TotalContributors = paidInvoices, CurrentAmount = currentAmount, - Active = active + Active = active, + DaysLeft = settings.EndDate.HasValue? (settings.EndDate - DateTime.UtcNow).Value.Days: (int?) null, + DaysLeftToStart = settings.StartDate.HasValue? (settings.StartDate - DateTime.UtcNow).Value.Days: (int?) null, + ShowProgress =active && settings.TargetAmount.HasValue, + ProgressPercentage = currentAmount/ settings.TargetAmount * 100 } }; } @@ -281,14 +298,19 @@ namespace BTCPayServer.Controllers } - public async Task GetApp(string appId, AppType appType) + public async Task GetApp(string appId, AppType appType, bool includeStore = false) { using (var ctx = _ContextFactory.CreateContext()) { - return await ctx.Apps - .Where(us => us.Id == appId && - us.AppType == appType.ToString()) - .FirstOrDefaultAsync(); + var query = ctx.Apps + .Where(us => us.Id == appId && + us.AppType == appType.ToString()); + + if (includeStore) + { + query = query.Include(data => data.StoreData); + } + return await query.FirstOrDefaultAsync(); } } diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index 1308ddd98..64ee40eae 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -38,6 +38,7 @@ using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Mvc.Cors.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; using System.Net; +using BTCPayServer.Hubs; using Meziantou.AspNetCore.BundleTagHelpers; using BTCPayServer.Security; @@ -78,7 +79,7 @@ namespace BTCPayServer.Hosting services.AddIdentity() .AddEntityFrameworkStores() .AddDefaultTokenProviders(); - + services.AddSignalR(); services.AddBTCPayServer(); services.AddMvc(o => { @@ -198,6 +199,10 @@ namespace BTCPayServer.Hosting AppPath = options.GetRootUri(), Authorization = new[] { new NeedRole(Roles.ServerAdmin) } }); + app.UseSignalR(route => + { + route.MapHub("/apps/crowdfund/hub"); + }); app.UseWebSockets(); app.UseStatusCodePages(); app.UseMvc(routes => diff --git a/BTCPayServer/Hubs/CrowdfundHub.cs b/BTCPayServer/Hubs/CrowdfundHub.cs new file mode 100644 index 000000000..72a2a7d9f --- /dev/null +++ b/BTCPayServer/Hubs/CrowdfundHub.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace BTCPayServer.Hubs +{ + public class CrowdfundHub: Hub + { + + } +} diff --git a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs index 6d4781525..56b117231 100644 --- a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs @@ -11,6 +11,9 @@ namespace BTCPayServer.Models.AppViewModels [Required] public string Description { get; set; } + public string MainImageUrl { get; set; } + + public string NotificationUrl { get; set; } [Required] public bool Enabled { get; set; } @@ -33,5 +36,7 @@ namespace BTCPayServer.Models.AppViewModels [MaxLength(500)] [Display(Name = "Custom bootstrap CSS file")] public string CustomCSSLink { get; set; } + + public string Tagline { get; set; } } } diff --git a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs index 547888577..457e79fb1 100644 --- a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs @@ -10,6 +10,7 @@ namespace BTCPayServer.Models.AppViewModels public string AppId { get; set; } public string Title { get; set; } public string Description { get; set; } + public string MainImageUrl { get; set; } public string CustomCSSLink { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } @@ -19,6 +20,7 @@ namespace BTCPayServer.Models.AppViewModels public bool EnforceTargetAmount { get; set; } public CrowdfundInfo Info { get; set; } + public string Tagline { get; set; } public class CrowdfundInfo @@ -26,14 +28,21 @@ namespace BTCPayServer.Models.AppViewModels public int TotalContributors { get; set; } public decimal CurrentAmount { get; set; } public bool Active { get; set; } + public bool ShowProgress { get; set; } + public decimal? ProgressPercentage { get; set; } + public int? DaysLeft{ get; set; } + public int? DaysLeftToStart{ get; set; } } + } public class ContributeToCrowdfund { + public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; } [Required] public decimal Amount { get; set; } public string Email { get; set; } + public bool RedirectToCheckout { get; set; } } } diff --git a/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml b/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml index 771843631..f830a41b3 100644 --- a/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml +++ b/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml @@ -23,6 +23,11 @@ * + +
+ * + +
* @@ -35,14 +40,27 @@ +
+
+ + + +
+
+ + +
+
+ +
diff --git a/BTCPayServer/Views/AppsPublic/ContributeForm.cshtml b/BTCPayServer/Views/AppsPublic/ContributeForm.cshtml new file mode 100644 index 000000000..7e9ec124b --- /dev/null +++ b/BTCPayServer/Views/AppsPublic/ContributeForm.cshtml @@ -0,0 +1,23 @@ +@model BTCPayServer.Models.AppViewModels.ContributeToCrowdfund + + +
+ +
+ + + +
+
+ +
+ +
+ @Model.ViewCrowdfundViewModel.TargetCurrency.ToUpperInvariant() +
+
+ +
+ + +
diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml deleted file mode 100644 index 791664f1e..000000000 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/ContributeForm.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@model BTCPayServer.Models.AppViewModels.ContributeToCrowdfund - - -
- - - -
diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml deleted file mode 100644 index 19a298b30..000000000 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml +++ /dev/null @@ -1,19 +0,0 @@ -@model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel -
- -
-
- -
-
- -
-

@Model.Title

-
-
- @Model.Description - -
-
-
 @Html.Raw(Model)
-
diff --git a/BTCPayServer/Views/AppsPublic/MinimalCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/MinimalCrowdfund.cshtml new file mode 100644 index 000000000..cfcb15ce0 --- /dev/null +++ b/BTCPayServer/Views/AppsPublic/MinimalCrowdfund.cshtml @@ -0,0 +1,101 @@ +@using BTCPayServer.Models.AppViewModels +@model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel +
+ + +
+ +
+ + @if (!string.IsNullOrEmpty(Model.MainImageUrl)) + { + Card image cap + } + @if (Model.Info.ShowProgress) + { +
+
+ @if (Model.Info.ProgressPercentage.Value > 0) + { + @(Model.Info.ProgressPercentage + "%") + } +
+
+ } +
+
+
+ +

+ @Model.Title + @if (!string.IsNullOrEmpty(Model.Tagline)) + { +

@Model.Tagline

+ } + + @if (Model.Info.DaysLeftToStart.HasValue && Model.Info.DaysLeftToStart > 0) + { + + @($"{Model.Info.DaysLeftToStart} day{(Model.Info.DaysLeftToStart.Value > 1 ? "s" : "")} left to start") + + + } + + +
+
    +
  • @(Model.EndDate.HasValue? $"Ends {Model.EndDate.Value:dddd, dd MMMM yyyy HH:mm}" : "No specific end date")
  • +
  • @(Model.TargetAmount.HasValue? $"{Model.TargetAmount:G29} {Model.TargetCurrency.ToUpperInvariant()} Goal" : + "No specific target goal")
  • +
  • @(Model.EnforceTargetAmount? $"Hardcap Goal" : "Softcap Goal")
  • +
+ +
+ @if (Model.Info.Active) + { +
+
+
+
@Model.Info.TotalContributors
+
Contributors
+
+
+
+
+
@Model.Info.CurrentAmount @Model.TargetCurrency.ToUpperInvariant()
+
Raised
+
+
+ + @if (Model.Info.DaysLeft.HasValue && Model.Info.DaysLeft > 0) + { +
+
+
@Model.Info.DaysLeft
+
Day@(Model.Info.DaysLeft.Value > 1 ? "s" : "") left
+
+
+ } + + +
+ } + + +
@Html.Raw(Model.Description)
+ @if (Model.Info.Active) + { +
+

Contribute

+ + } +
+
+ + +
+
diff --git a/BTCPayServer/Views/AppsPublic/ViewCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/ViewCrowdfund.cshtml index 7bf6eee59..ec7b3c3e8 100644 --- a/BTCPayServer/Views/AppsPublic/ViewCrowdfund.cshtml +++ b/BTCPayServer/Views/AppsPublic/ViewCrowdfund.cshtml @@ -1,7 +1,5 @@ @addTagHelper *, Meziantou.AspNetCore.BundleTagHelpers @inject BTCPayServer.HostedServices.CssThemeManager themeManager - -@using System.Security.AccessControl @model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel @{ ViewData["Title"] = Model.Title; @@ -15,7 +13,7 @@ - + @if (Model.CustomCSSLink != null) { @@ -23,7 +21,7 @@