mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
start contrib perks
This commit is contained in:
@@ -31,6 +31,7 @@ namespace BTCPayServer.Controllers
|
|||||||
public string NotificationUrl { get; set; }
|
public string NotificationUrl { get; set; }
|
||||||
public string Tagline { get; set; }
|
public string Tagline { get; set; }
|
||||||
public string EmbeddedCSS { get; set; }
|
public string EmbeddedCSS { get; set; }
|
||||||
|
public string PerksTemplate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,16 +58,31 @@ namespace BTCPayServer.Controllers
|
|||||||
CustomCSSLink = settings.CustomCSSLink,
|
CustomCSSLink = settings.CustomCSSLink,
|
||||||
NotificationUrl = settings.NotificationUrl,
|
NotificationUrl = settings.NotificationUrl,
|
||||||
Tagline = settings.Tagline,
|
Tagline = settings.Tagline,
|
||||||
|
PerksTemplate = settings.PerksTemplate
|
||||||
};
|
};
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{appId}/settings/crowdfund")]
|
[Route("{appId}/settings/crowdfund")]
|
||||||
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdateCrowdfundViewModel vm)
|
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm)
|
||||||
{
|
{
|
||||||
if (_AppsHelper.GetCurrencyData(vm.TargetCurrency, false) == null)
|
if (_AppsHelper.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||||
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_AppsHelper.Parse(vm.PerksTemplate, vm.TargetCurrency);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(vm.PerksTemplate), "Invalid template");
|
||||||
|
}
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
||||||
if (app == null)
|
if (app == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@@ -84,7 +100,8 @@ namespace BTCPayServer.Controllers
|
|||||||
MainImageUrl = vm.MainImageUrl,
|
MainImageUrl = vm.MainImageUrl,
|
||||||
EmbeddedCSS = vm.EmbeddedCSS,
|
EmbeddedCSS = vm.EmbeddedCSS,
|
||||||
NotificationUrl = vm.NotificationUrl,
|
NotificationUrl = vm.NotificationUrl,
|
||||||
Tagline = vm.Tagline
|
Tagline = vm.Tagline,
|
||||||
|
PerksTemplate = vm.PerksTemplate
|
||||||
});
|
});
|
||||||
await UpdateAppSettings(app);
|
await UpdateAppSettings(app);
|
||||||
_EventAggregator.Publish(new CrowdfundAppUpdated()
|
_EventAggregator.Publish(new CrowdfundAppUpdated()
|
||||||
|
|||||||
@@ -120,9 +120,17 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
StatusMessage = "App successfully created";
|
StatusMessage = "App successfully created";
|
||||||
CreatedAppId = id;
|
CreatedAppId = id;
|
||||||
if (appType == AppType.PointOfSale)
|
|
||||||
return RedirectToAction(nameof(UpdatePointOfSale), new { appId = id });
|
switch (appType)
|
||||||
return RedirectToAction(nameof(ListApps));
|
{
|
||||||
|
case AppType.PointOfSale:
|
||||||
|
return RedirectToAction(nameof(UpdatePointOfSale), new { appId = id });
|
||||||
|
case AppType.Crowdfund:
|
||||||
|
return RedirectToAction(nameof(UpdateCrowdfund), new { appId = id });
|
||||||
|
default:
|
||||||
|
return RedirectToAction(nameof(ListApps));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -113,18 +113,40 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
var settings = app.GetSettings<CrowdfundSettings>();
|
var settings = app.GetSettings<CrowdfundSettings>();
|
||||||
var store = await _AppsHelper.GetStore(app);
|
var store = await _AppsHelper.GetStore(app);
|
||||||
|
string title = null;
|
||||||
|
var price = 0.0m;
|
||||||
|
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||||
|
{
|
||||||
|
var choices = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||||
|
var choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||||
|
if (choice == null)
|
||||||
|
return NotFound();
|
||||||
|
title = choice.Title;
|
||||||
|
price = choice.Price.Value;
|
||||||
|
if (request.Amount > price)
|
||||||
|
price = request.Amount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
price = request.Amount;
|
||||||
|
title = settings.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
|
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
|
||||||
var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice()
|
var invoice = await _InvoiceController.CreateInvoiceCore(new Invoice()
|
||||||
{
|
{
|
||||||
OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}",
|
OrderId = $"{CrowdfundHubStreamer.CrowdfundInvoiceOrderIdPrefix}{appId}",
|
||||||
Currency = settings.TargetCurrency,
|
Currency = settings.TargetCurrency,
|
||||||
|
ItemCode = request.ChoiceKey ?? string.Empty,
|
||||||
|
ItemDesc = title,
|
||||||
BuyerEmail = request.Email,
|
BuyerEmail = request.Email,
|
||||||
Price = request.Amount,
|
Price = price,
|
||||||
NotificationURL = settings.NotificationUrl,
|
NotificationURL = settings.NotificationUrl,
|
||||||
FullNotifications = true,
|
FullNotifications = true,
|
||||||
ExtendedNotifications = true,
|
ExtendedNotifications = true,
|
||||||
RedirectURL = HttpContext.Request.GetAbsoluteRoot()+ "/apps/{appId}/crowdfund",
|
RedirectURL = request.RedirectUrl,
|
||||||
|
|
||||||
|
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||||
if (request.RedirectToCheckout)
|
if (request.RedirectToCheckout)
|
||||||
@@ -195,7 +217,7 @@ namespace BTCPayServer.Controllers
|
|||||||
OrderId = orderId,
|
OrderId = orderId,
|
||||||
NotificationURL = notificationUrl,
|
NotificationURL = notificationUrl,
|
||||||
RedirectURL = redirectUrl,
|
RedirectURL = redirectUrl,
|
||||||
FullNotifications = true
|
FullNotifications = true,
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot());
|
}, store, HttpContext.Request.GetAbsoluteRoot());
|
||||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice", new { invoiceId = invoice.Data.Id });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ namespace BTCPayServer.Hubs
|
|||||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
||||||
var result = await GetInfo(app, _InvoiceRepository, _RateFetcher,
|
var result = await GetInfo(app);
|
||||||
_BtcPayNetworkProvider);
|
|
||||||
entry.SetValue(result);
|
entry.SetValue(result);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@@ -191,26 +190,27 @@ namespace BTCPayServer.Hubs
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, InvoiceRepository invoiceRepository,
|
private async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, string statusMessage= null)
|
||||||
RateFetcher rateFetcher, BTCPayNetworkProvider btcPayNetworkProvider, string statusMessage= null)
|
|
||||||
{
|
{
|
||||||
var settings = appData.GetSettings<AppsController.CrowdfundSettings>();
|
var settings = appData.GetSettings<AppsController.CrowdfundSettings>();
|
||||||
var invoices = await GetInvoicesForApp(appData, invoiceRepository);
|
var invoices = await GetInvoicesForApp(appData, _InvoiceRepository);
|
||||||
|
|
||||||
|
|
||||||
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(btcPayNetworkProvider);
|
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider);
|
||||||
var currentAmount = await GetCurrentContributionAmount(
|
var currentAmount = await GetCurrentContributionAmount(
|
||||||
invoices.Where(entity => entity.Status == InvoiceStatus.Complete).ToArray(),
|
invoices.Where(entity => entity.Status == InvoiceStatus.Complete).ToArray(),
|
||||||
settings.TargetCurrency, rateFetcher, rateRules);
|
settings.TargetCurrency, _RateFetcher, rateRules);
|
||||||
var currentPendingAmount = await GetCurrentContributionAmount(
|
var currentPendingAmount = await GetCurrentContributionAmount(
|
||||||
invoices.Where(entity => entity.Status != InvoiceStatus.Complete).ToArray(),
|
invoices.Where(entity => entity.Status != InvoiceStatus.Complete).ToArray(),
|
||||||
settings.TargetCurrency, rateFetcher, rateRules);
|
settings.TargetCurrency, _RateFetcher, rateRules);
|
||||||
|
|
||||||
|
|
||||||
var active = (settings.StartDate == null || DateTime.Now >= settings.StartDate) &&
|
var active = (settings.StartDate == null || DateTime.Now >= settings.StartDate) &&
|
||||||
(settings.EndDate == null || DateTime.Now <= settings.EndDate) &&
|
(settings.EndDate == null || DateTime.Now <= settings.EndDate) &&
|
||||||
(!settings.EnforceTargetAmount || settings.TargetAmount > currentAmount);
|
(!settings.EnforceTargetAmount || settings.TargetAmount > currentAmount);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return new ViewCrowdfundViewModel()
|
return new ViewCrowdfundViewModel()
|
||||||
{
|
{
|
||||||
Title = settings.Title,
|
Title = settings.Title,
|
||||||
@@ -227,6 +227,7 @@ namespace BTCPayServer.Hubs
|
|||||||
TargetCurrency = settings.TargetCurrency,
|
TargetCurrency = settings.TargetCurrency,
|
||||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||||
StatusMessage = statusMessage,
|
StatusMessage = statusMessage,
|
||||||
|
Perks = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency),
|
||||||
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
||||||
{
|
{
|
||||||
TotalContributors = invoices.Length,
|
TotalContributors = invoices.Length,
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ namespace BTCPayServer.Models.AppViewModels
|
|||||||
[Display(Name = "Do not allow additional contributions after target has been reached")]
|
[Display(Name = "Do not allow additional contributions after target has been reached")]
|
||||||
public bool EnforceTargetAmount { get; set; }
|
public bool EnforceTargetAmount { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Contribution Perks Template")]
|
||||||
|
public string PerksTemplate { get; set; }
|
||||||
|
|
||||||
[MaxLength(500)]
|
[MaxLength(500)]
|
||||||
[Display(Name = "Custom bootstrap CSS file")]
|
[Display(Name = "Custom bootstrap CSS file")]
|
||||||
public string CustomCSSLink { get; set; }
|
public string CustomCSSLink { get; set; }
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace BTCPayServer.Models.AppViewModels
|
|||||||
|
|
||||||
public CrowdfundInfo Info { get; set; }
|
public CrowdfundInfo Info { get; set; }
|
||||||
public string Tagline { get; set; }
|
public string Tagline { get; set; }
|
||||||
|
public ViewPointOfSaleViewModel.Item[] Perks { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public class CrowdfundInfo
|
public class CrowdfundInfo
|
||||||
@@ -46,6 +47,8 @@ namespace BTCPayServer.Models.AppViewModels
|
|||||||
public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; }
|
public ViewCrowdfundViewModel ViewCrowdfundViewModel { get; set; }
|
||||||
[Required] public decimal Amount { get; set; }
|
[Required] public decimal Amount { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public bool RedirectToCheckout { get; set; }
|
public string ChoiceKey { get; set; }
|
||||||
|
public bool RedirectToCheckout { get; set; }
|
||||||
|
public string RedirectUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
ViewData["Title"] = "Update Crowdfund";
|
ViewData["Title"] = "Update Crowdfund";
|
||||||
}
|
}
|
||||||
<section>
|
<section>
|
||||||
|
<div class="modal" id="product-modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Contribution Perks Management</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="js-product-save btn btn-primary">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12 text-center">
|
<div class="col-lg-12 text-center">
|
||||||
@@ -61,6 +79,19 @@
|
|||||||
<input asp-for="EndDate" class="form-control" />
|
<input asp-for="EndDate" class="form-control" />
|
||||||
<span asp-validation-for="EndDate" class="text-danger"></span>
|
<span asp-validation-for="EndDate" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">Contribution Perks</label>
|
||||||
|
<div class="mb-3">
|
||||||
|
<a class="js-product-add btn btn-secondary" href="#" data-toggle="modal" data-target="#product-modal"><i class="fa fa-plus fa-fw"></i> Add Product</a>
|
||||||
|
</div>
|
||||||
|
<div class="js-products bg-light row p-3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="PerksTemplate" class="control-label"></label>*
|
||||||
|
<textarea asp-for="PerksTemplate" rows="10" cols="40" class="js-product-template form-control"></textarea>
|
||||||
|
<span asp-validation-for="PerksTemplate" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="CustomCSSLink" class="control-label"></label>
|
<label asp-for="CustomCSSLink" class="control-label"></label>
|
||||||
<a href="https://docs.btcpayserver.org/development/theme#bootstrap-themes" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
<a href="https://docs.btcpayserver.org/development/theme#bootstrap-themes" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||||
@@ -104,5 +135,56 @@
|
|||||||
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">
|
||||||
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
<script src="~/vendor/highlightjs/highlight.min.js"></script>
|
||||||
<script>hljs.initHighlightingOnLoad();</script>
|
<script>hljs.initHighlightingOnLoad();</script>
|
||||||
|
|
||||||
|
<script id="template-product-item" type="text/template">
|
||||||
|
<div class="col-sm-4 col-md-3 mb-3">
|
||||||
|
<div class="card">
|
||||||
|
{image}
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title">{title}</h6>
|
||||||
|
<a href="#" class="js-product-edit btn btn-primary" data-toggle="modal" data-target="#product-modal">Edit</a>
|
||||||
|
<a href="#" class="js-product-remove btn btn-danger"><i class="fa fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="template-product-content" type="text/template">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input class="js-product-id" type="hidden" name="id" value="{id}">
|
||||||
|
<input class="js-product-index" type="hidden" name="index" value="{index}">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label>Title</label>*
|
||||||
|
<input type="text" class="js-product-title form-control mb-2" value="{title}" autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<label>Price</label>*
|
||||||
|
<input type="text" class="js-product-price form-control mb-2" value="{price}" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<label>Custom price</label>
|
||||||
|
<select class="js-product-custom form-control">
|
||||||
|
{custom}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<label>Image</label>
|
||||||
|
<input type="text" class="js-product-image form-control mb-2" value="{image}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<label>Description</label>
|
||||||
|
<textarea rows="3" cols="40" class="js-product-description form-control mb-2">{description}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="~/products/js/products.js"></script>
|
||||||
|
<script src="~/products/js/products.jquery.js"></script>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<h2 class="text-muted" v-if="srvModel.tagline">{{srvModel.tagline}}</h2>
|
<h2 class="text-muted" v-if="srvModel.tagline">{{srvModel.tagline}}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 col-md-3 col-lg-2">
|
<div class="col-sm-12 col-md-3 col-lg-2">
|
||||||
<button v-if="srvModel.info.active" class="btn btn-lg btn-primary" v-on:click="contributeModalOpen = true">Contribute</button>
|
<button v-if="srvModel.info.active" class="btn btn-lg btn-primary w-100" v-on:click="contributeModalOpen = true">Contribute</button>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ var resizeCanvas = function() {
|
|||||||
window.addEventListener("resize", resizeCanvas);
|
window.addEventListener("resize", resizeCanvas);
|
||||||
// addClickListeners();
|
// addClickListeners();
|
||||||
|
|
||||||
handleInactiveUser();
|
// handleInactiveUser();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function handleInactiveUser() {
|
function handleInactiveUser() {
|
||||||
|
|||||||
Reference in New Issue
Block a user