diff --git a/BTCPayServer/Controllers/AppsController.Crowdsale.cs b/BTCPayServer/Controllers/AppsController.Crowdsale.cs index 0570a1ee4..55e7aac0b 100644 --- a/BTCPayServer/Controllers/AppsController.Crowdsale.cs +++ b/BTCPayServer/Controllers/AppsController.Crowdsale.cs @@ -37,6 +37,8 @@ namespace BTCPayServer.Controllers public string DisqusShortname { get; set; } public bool AnimationsEnabled { get; set; } public bool UseInvoiceAmount { get; set; } + public int ResetEveryAmount { get; set; } + public CrowdfundResetEvery ResetEvery { get; set; } } @@ -68,7 +70,9 @@ namespace BTCPayServer.Controllers SoundsEnabled = settings.SoundsEnabled, DisqusShortname = settings.DisqusShortname, AnimationsEnabled = settings.AnimationsEnabled, - UseInvoiceAmount = settings.UseInvoiceAmount + UseInvoiceAmount = settings.UseInvoiceAmount, + ResetEveryAmount = settings.ResetEveryAmount, + ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery), }; return View(vm); } @@ -116,7 +120,8 @@ namespace BTCPayServer.Controllers SoundsEnabled = vm.SoundsEnabled, DisqusShortname = vm.DisqusShortname, AnimationsEnabled = vm.AnimationsEnabled, - + ResetEveryAmount = vm.ResetEveryAmount, + ResetEvery = Enum.Parse(vm.ResetEvery), UseInvoiceAmount = vm.UseInvoiceAmount }); await UpdateAppSettings(app); diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index 1f0ef7eaf..365d91837 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -122,12 +122,14 @@ namespace BTCPayServer.Controllers var info = await _CrowdfundHubStreamer.GetCrowdfundInfo(appId); if(!isAdmin && + ((settings.StartDate.HasValue && DateTime.Now < settings.StartDate) || (settings.EndDate.HasValue && DateTime.Now > settings.EndDate) || - (settings.EnforceTargetAmount && (info.Info.PendingProgressPercentage.GetValueOrDefault(0) + info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100))) + (settings.EnforceTargetAmount && + (info.Info.PendingProgressPercentage.GetValueOrDefault(0) + + info.Info.ProgressPercentage.GetValueOrDefault(0)) >= 100))) { return NotFound(); - } var store = await _AppsHelper.GetStore(app); diff --git a/BTCPayServer/Crowdfund/CrowdfundHub.cs b/BTCPayServer/Crowdfund/CrowdfundHub.cs index 7a3ca49b7..a2e00ebec 100644 --- a/BTCPayServer/Crowdfund/CrowdfundHub.cs +++ b/BTCPayServer/Crowdfund/CrowdfundHub.cs @@ -12,6 +12,7 @@ namespace BTCPayServer.Hubs public const string InvoiceCreated = "InvoiceCreated"; public const string PaymentReceived = "PaymentReceived"; public const string InfoUpdated = "InfoUpdated"; + public const string InvoiceError = "InvoiceError"; private readonly AppsPublicController _AppsPublicController; public CrowdfundHub(AppsPublicController appsPublicController) @@ -35,7 +36,19 @@ namespace BTCPayServer.Hubs model.RedirectToCheckout = false; _AppsPublicController.ControllerContext.HttpContext = Context.GetHttpContext(); var result = await _AppsPublicController.ContributeToCrowdfund(Context.Items["app"].ToString(), model); - await Clients.Caller.SendCoreAsync(InvoiceCreated, new[] {(result as OkObjectResult)?.Value.ToString()}); + switch (result) + { + case OkObjectResult okObjectResult: + await Clients.Caller.SendCoreAsync(InvoiceCreated, new[] {okObjectResult.Value.ToString()}); + break; + case ObjectResult objectResult: + await Clients.Caller.SendCoreAsync(InvoiceError, new[] {objectResult.Value}); + break; + default: + await Clients.Caller.SendCoreAsync(InvoiceError, System.Array.Empty()); + break; + } + } } diff --git a/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs b/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs index b813fafcb..ab0c0f3ab 100644 --- a/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs +++ b/BTCPayServer/Crowdfund/CrowdfundHubStreamer.cs @@ -45,7 +45,7 @@ namespace BTCPayServer.Hubs _InvoiceRepository = invoiceRepository; SubscribeToEvents(); } - + public Task GetCrowdfundInfo(string appId) { return _MemoryCache.GetOrCreateAsync(GetCacheKey(appId), async entry => @@ -178,8 +178,41 @@ namespace BTCPayServer.Hubs private async Task GetInfo(AppData appData, string statusMessage= null) { var settings = appData.GetSettings(); - var invoices = await GetInvoicesForApp(appData, _InvoiceRepository); + var resetEvery = settings.StartDate.HasValue? settings.ResetEvery : CrowdfundResetEvery.Never; + DateTime? lastResetDate = null; + DateTime? nextResetDate = null; + if (resetEvery != CrowdfundResetEvery.Never) + { + lastResetDate = settings.StartDate.Value; + + nextResetDate = lastResetDate.Value; + while (DateTime.Now >= nextResetDate) + { + lastResetDate = nextResetDate; + switch (resetEvery) + { + case CrowdfundResetEvery.Hour: + nextResetDate = lastResetDate.Value.AddHours(settings.ResetEveryAmount); + break; + case CrowdfundResetEvery.Day: + nextResetDate = lastResetDate.Value.AddDays(settings.ResetEveryAmount); + break; + case CrowdfundResetEvery.Month: + + nextResetDate = lastResetDate.Value.AddMonths(settings.ResetEveryAmount); + break; + case CrowdfundResetEvery.Year: + nextResetDate = lastResetDate.Value.AddYears(settings.ResetEveryAmount); + break; + } + } + } + + + + + var invoices = await GetInvoicesForApp(appData, lastResetDate); var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete).ToArray(); var pendingInvoices = invoices.Where(entity => entity.Status != InvoiceStatus.Complete).ToArray(); @@ -190,7 +223,7 @@ namespace BTCPayServer.Hubs var currentAmount = await GetCurrentContributionAmount( paymentStats, - settings.TargetCurrency, _RateFetcher, rateRules); + settings.TargetCurrency, _RateFetcher, rateRules); var currentPendingAmount = await GetCurrentContributionAmount( pendingPaymentStats, settings.TargetCurrency, _RateFetcher, rateRules); @@ -217,6 +250,8 @@ namespace BTCPayServer.Hubs SoundsEnabled = settings.SoundsEnabled, DisqusShortname = settings.DisqusShortname, AnimationsEnabled = settings.AnimationsEnabled, + ResetEveryAmount = settings.ResetEveryAmount, + ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery),settings.ResetEvery), Info = new ViewCrowdfundViewModel.CrowdfundInfo() { TotalContributors = invoices.Length, @@ -226,21 +261,24 @@ namespace BTCPayServer.Hubs PendingProgressPercentage = ( currentPendingAmount/ settings.TargetAmount) * 100, LastUpdated = DateTime.Now, PaymentStats = paymentStats, - PendingPaymentStats = pendingPaymentStats + PendingPaymentStats = pendingPaymentStats, + LastResetDate = lastResetDate, + NextResetDate = nextResetDate } }; } - private static async Task GetInvoicesForApp(AppData appData, InvoiceRepository invoiceRepository) + private async Task GetInvoicesForApp(AppData appData, DateTime? startDate = null) { - return await invoiceRepository.GetInvoices(new InvoiceQuery() + return await _InvoiceRepository.GetInvoices(new InvoiceQuery() { OrderId = $"{CrowdfundInvoiceOrderIdPrefix}{appData.Id}", Status = new string[]{ InvoiceState.ToString(InvoiceStatus.New), InvoiceState.ToString(InvoiceStatus.Paid), InvoiceState.ToString(InvoiceStatus.Confirmed), - InvoiceState.ToString(InvoiceStatus.Complete)} + InvoiceState.ToString(InvoiceStatus.Complete)}, + StartDate = startDate }); } diff --git a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs index 3c20f298b..8bdb179b4 100644 --- a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs @@ -1,43 +1,39 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace BTCPayServer.Models.AppViewModels { public class UpdateCrowdfundViewModel { - [Required] - [MaxLength(30)] - public string Title { get; set; } - - [MaxLength(50)] - public string Tagline { get; set; } - - [Required] - public string Description { get; set; } + [Required] [MaxLength(30)] public string Title { get; set; } + + [MaxLength(50)] public string Tagline { get; set; } + + [Required] public string Description { get; set; } public string MainImageUrl { get; set; } - + public string NotificationUrl { get; set; } - + [Required] - [Display(Name = "Enabled, Allow crowdfund to be publicly visible( still visible to you)")] public bool Enabled { get; set; } = false; - + [Required] [Display(Name = "Enable background animations on new payments")] public bool AnimationsEnabled { get; set; } = true; - + [Required] [Display(Name = "Enable sounds on new payments")] public bool SoundsEnabled { get; set; } = true; - + [Required] [Display(Name = "Enable Disqus Comments")] public bool DisqusEnabled { get; set; } = true; - - [Display(Name = "Disqus Shortname")] - public string DisqusShortname { get; set; } - + + [Display(Name = "Disqus Shortname")] public string DisqusShortname { get; set; } + public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } @@ -45,17 +41,24 @@ namespace BTCPayServer.Models.AppViewModels [MaxLength(5)] [Display(Name = "The primary currency used for targets and stats")] public string TargetCurrency { get; set; } = "BTC"; - + [Display(Name = "Set a Target amount ")] public decimal? TargetAmount { get; set; } - - + + + public IEnumerable ResetEveryValues = Enum.GetNames(typeof(CrowdfundResetEvery)); + + [Display(Name = "Reset goal every")] public string ResetEvery { get; set; } = nameof(CrowdfundResetEvery.Never); + + public int ResetEveryAmount { get; set; } = 1; + + [Display(Name = "Do not allow additional contributions after target has been reached")] public bool EnforceTargetAmount { get; set; } [Display(Name = "Contribution Perks Template")] public string PerksTemplate { get; set; } - + [MaxLength(500)] [Display(Name = "Custom bootstrap CSS file")] public string CustomCSSLink { get; set; } @@ -65,4 +68,13 @@ namespace BTCPayServer.Models.AppViewModels [Display(Name = "Base the contributed goal amount on the invoice amount and not actual payments")] public bool UseInvoiceAmount { get; set; } = true; } + + public enum CrowdfundResetEvery + { + Never, + Hour, + Day, + Month, + Year + } } diff --git a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs index 1d5289d1e..ed295cf6b 100644 --- a/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewCrowdfundViewModel.cs @@ -29,6 +29,8 @@ namespace BTCPayServer.Models.AppViewModels public bool SoundsEnabled { get; set; } public string DisqusShortname { get; set; } public bool AnimationsEnabled { get; set; } + public int ResetEveryAmount { get; set; } + public string ResetEvery { get; set; } public class CrowdfundInfo @@ -41,6 +43,8 @@ namespace BTCPayServer.Models.AppViewModels public DateTime LastUpdated { get; set; } public Dictionary PaymentStats { get; set; } public Dictionary PendingPaymentStats { get; set; } + public DateTime? LastResetDate { get; set; } + public DateTime? NextResetDate { get; set; } } } diff --git a/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml b/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml index dcc8c8f5c..e63cd160e 100644 --- a/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml +++ b/BTCPayServer/Views/Apps/UpdateCrowdfund.cshtml @@ -74,6 +74,20 @@ +
+ +
+ + + +
+
+
* diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml index 9b2cae77c..36af9da87 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml @@ -50,6 +50,10 @@ { @Model.TargetAmount @Model.TargetCurrency + @if (Model.ResetEveryAmount > 0 && Model.ResetEvery != nameof(CrowdfundResetEvery.Never)) + { + Dynamic + } @if (Model.EnforceTargetAmount) { Hardcap Goal diff --git a/BTCPayServer/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml b/BTCPayServer/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml index df828f8ee..89a2d1eb0 100644 --- a/BTCPayServer/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml +++ b/BTCPayServer/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml @@ -19,7 +19,8 @@ {{srvModel.targetAmount}} {{targetCurrency}} - + Dynamic Hardcap Goal Softcap Goal @@ -51,7 +52,7 @@
{{srvModel.info.currentAmount + srvModel.info.currentPendingAmount }} {{targetCurrency}}
Raised
-
+
{{srvModel.info.progressPercentage + srvModel.info.pendingProgressPercentage }}%
Of Goal
@@ -98,6 +99,9 @@ {{stat.label}} {{stat.value}} + + + Goal resets every {{srvModel.resetEveryAmount}} {{srvModel.resetEvery}} {{srvModel.resetEveryAmount>1?'s': ''}}