mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 14:04:26 +01:00
Rename AppsHelper to AppService
This commit is contained in:
@@ -236,7 +236,7 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||||
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
|
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
|
||||||
Assert.Contains(AppsHelper.GetAppInternalTag(appId), invoiceEntity.InternalTags);
|
Assert.Contains(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
|
||||||
|
|
||||||
crowdfundViewModel.Enabled = true;
|
crowdfundViewModel.Enabled = true;
|
||||||
crowdfundViewModel.EndDate = null;
|
crowdfundViewModel.EndDate = null;
|
||||||
@@ -256,7 +256,7 @@ namespace BTCPayServer.Tests
|
|||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
|
||||||
Assert.DoesNotContain(AppsHelper.GetAppInternalTag(appId), invoiceEntity.InternalTags);
|
Assert.DoesNotContain(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace BTCPayServer.Controllers
|
|||||||
ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery),
|
ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery),
|
||||||
UseAllStoreInvoices = app.TagAllInvoices,
|
UseAllStoreInvoices = app.TagAllInvoices,
|
||||||
AppId = appId,
|
AppId = appId,
|
||||||
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppsHelper.GetCrowdfundOrderId(appId)}",
|
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetCrowdfundOrderId(appId)}",
|
||||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||||
SortPerksByPopularity = settings.SortPerksByPopularity
|
SortPerksByPopularity = settings.SortPerksByPopularity
|
||||||
};
|
};
|
||||||
@@ -98,7 +98,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_AppsHelper.Parse(vm.PerksTemplate, vm.TargetCurrency).ToString();
|
_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency).ToString();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var items = _AppsHelper.Parse(settings.Template, settings.Currency);
|
var items = _AppService.Parse(settings.Template, settings.Currency);
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
builder.AppendLine($"<form method=\"POST\" action=\"{encoder.Encode(appUrl)}\">");
|
||||||
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
builder.AppendLine($" <input type=\"hidden\" name=\"email\" value=\"customer@example.com\" />");
|
||||||
@@ -142,7 +142,7 @@ namespace BTCPayServer.Controllers
|
|||||||
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_AppsHelper.Parse(vm.Template, vm.Currency);
|
_AppService.Parse(vm.Template, vm.Currency);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace BTCPayServer.Controllers
|
|||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
HtmlSanitizer htmlSanitizer,
|
HtmlSanitizer htmlSanitizer,
|
||||||
AppsHelper appsHelper)
|
AppService AppService)
|
||||||
{
|
{
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_ContextFactory = contextFactory;
|
_ContextFactory = contextFactory;
|
||||||
@@ -38,7 +38,7 @@ namespace BTCPayServer.Controllers
|
|||||||
_NetworkProvider = networkProvider;
|
_NetworkProvider = networkProvider;
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_htmlSanitizer = htmlSanitizer;
|
_htmlSanitizer = htmlSanitizer;
|
||||||
_AppsHelper = appsHelper;
|
_AppService = AppService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserManager<ApplicationUser> _UserManager;
|
private UserManager<ApplicationUser> _UserManager;
|
||||||
@@ -47,7 +47,7 @@ namespace BTCPayServer.Controllers
|
|||||||
private BTCPayNetworkProvider _NetworkProvider;
|
private BTCPayNetworkProvider _NetworkProvider;
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
private readonly HtmlSanitizer _htmlSanitizer;
|
private readonly HtmlSanitizer _htmlSanitizer;
|
||||||
private AppsHelper _AppsHelper;
|
private AppService _AppService;
|
||||||
|
|
||||||
[TempData]
|
[TempData]
|
||||||
public string StatusMessage { get; set; }
|
public string StatusMessage { get; set; }
|
||||||
@@ -55,7 +55,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
public async Task<IActionResult> ListApps()
|
public async Task<IActionResult> ListApps()
|
||||||
{
|
{
|
||||||
var apps = await _AppsHelper.GetAllApps(GetUserId());
|
var apps = await _AppService.GetAllApps(GetUserId());
|
||||||
return View(new ListAppsViewModel()
|
return View(new ListAppsViewModel()
|
||||||
{
|
{
|
||||||
Apps = apps
|
Apps = apps
|
||||||
@@ -69,7 +69,7 @@ namespace BTCPayServer.Controllers
|
|||||||
var appData = await GetOwnedApp(appId);
|
var appData = await GetOwnedApp(appId);
|
||||||
if (appData == null)
|
if (appData == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
if (await _AppsHelper.DeleteApp(appData))
|
if (await _AppService.DeleteApp(appData))
|
||||||
StatusMessage = "App removed successfully";
|
StatusMessage = "App removed successfully";
|
||||||
return RedirectToAction(nameof(ListApps));
|
return RedirectToAction(nameof(ListApps));
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ namespace BTCPayServer.Controllers
|
|||||||
[Route("create")]
|
[Route("create")]
|
||||||
public async Task<IActionResult> CreateApp()
|
public async Task<IActionResult> CreateApp()
|
||||||
{
|
{
|
||||||
var stores = await _AppsHelper.GetOwnedStores(GetUserId());
|
var stores = await _AppService.GetOwnedStores(GetUserId());
|
||||||
if (stores.Length == 0)
|
if (stores.Length == 0)
|
||||||
{
|
{
|
||||||
StatusMessage = new StatusMessageModel()
|
StatusMessage = new StatusMessageModel()
|
||||||
@@ -98,7 +98,7 @@ namespace BTCPayServer.Controllers
|
|||||||
[Route("create")]
|
[Route("create")]
|
||||||
public async Task<IActionResult> CreateApp(CreateAppViewModel vm)
|
public async Task<IActionResult> CreateApp(CreateAppViewModel vm)
|
||||||
{
|
{
|
||||||
var stores = await _AppsHelper.GetOwnedStores(GetUserId());
|
var stores = await _AppService.GetOwnedStores(GetUserId());
|
||||||
if (stores.Length == 0)
|
if (stores.Length == 0)
|
||||||
{
|
{
|
||||||
StatusMessage = new StatusMessageModel()
|
StatusMessage = new StatusMessageModel()
|
||||||
@@ -168,7 +168,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
private Task<AppData> GetOwnedApp(string appId, AppType? type = null)
|
private Task<AppData> GetOwnedApp(string appId, AppType? type = null)
|
||||||
{
|
{
|
||||||
return _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, type);
|
return _AppService.GetAppDataIfOwner(GetUserId(), appId, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
public class AppsPublicController : Controller
|
public class AppsPublicController : Controller
|
||||||
{
|
{
|
||||||
public AppsPublicController(AppsHelper appsHelper,
|
public AppsPublicController(AppService AppService,
|
||||||
InvoiceController invoiceController,
|
InvoiceController invoiceController,
|
||||||
UserManager<ApplicationUser> userManager)
|
UserManager<ApplicationUser> userManager)
|
||||||
{
|
{
|
||||||
_AppsHelper = appsHelper;
|
_AppService = AppService;
|
||||||
_InvoiceController = invoiceController;
|
_InvoiceController = invoiceController;
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppsHelper _AppsHelper;
|
private AppService _AppService;
|
||||||
private InvoiceController _InvoiceController;
|
private InvoiceController _InvoiceController;
|
||||||
private readonly CrowdfundHubStreamer _CrowdfundHubStreamer;
|
private readonly CrowdfundHubStreamer _CrowdfundHubStreamer;
|
||||||
private readonly UserManager<ApplicationUser> _UserManager;
|
private readonly UserManager<ApplicationUser> _UserManager;
|
||||||
@@ -50,12 +50,12 @@ namespace BTCPayServer.Controllers
|
|||||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
public async Task<IActionResult> ViewPointOfSale(string appId)
|
public async Task<IActionResult> ViewPointOfSale(string appId)
|
||||||
{
|
{
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
||||||
if (app == null)
|
if (app == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||||
|
|
||||||
var numberFormatInfo = _AppsHelper.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppsHelper.Currencies.GetNumberFormatInfo("USD");
|
var numberFormatInfo = _AppService.Currencies.GetNumberFormatInfo(settings.Currency) ?? _AppService.Currencies.GetNumberFormatInfo("USD");
|
||||||
double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits));
|
double step = Math.Pow(10, -(numberFormatInfo.CurrencyDecimalDigits));
|
||||||
|
|
||||||
return View(new ViewPointOfSaleViewModel()
|
return View(new ViewPointOfSaleViewModel()
|
||||||
@@ -75,7 +75,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
Prefixed = new[] { 0, 2 }.Contains(numberFormatInfo.CurrencyPositivePattern),
|
||||||
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
SymbolSpace = new[] { 2, 3 }.Contains(numberFormatInfo.CurrencyPositivePattern)
|
||||||
},
|
},
|
||||||
Items = _AppsHelper.Parse(settings.Template, settings.Currency),
|
Items = _AppService.Parse(settings.Template, settings.Currency),
|
||||||
ButtonText = settings.ButtonText,
|
ButtonText = settings.ButtonText,
|
||||||
CustomButtonText = settings.CustomButtonText,
|
CustomButtonText = settings.CustomButtonText,
|
||||||
CustomTipText = settings.CustomTipText,
|
CustomTipText = settings.CustomTipText,
|
||||||
@@ -92,13 +92,13 @@ namespace BTCPayServer.Controllers
|
|||||||
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
||||||
|
|
||||||
{
|
{
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
|
||||||
if (app == null)
|
if (app == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var settings = app.GetSettings<CrowdfundSettings>();
|
var settings = app.GetSettings<CrowdfundSettings>();
|
||||||
|
|
||||||
var isAdmin = await _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
||||||
|
|
||||||
var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency );
|
var hasEnoughSettingsToLoad = !string.IsNullOrEmpty(settings.TargetCurrency );
|
||||||
if (!hasEnoughSettingsToLoad)
|
if (!hasEnoughSettingsToLoad)
|
||||||
@@ -108,11 +108,11 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
return NotFound("A Target Currency must be set for this app in order to be loadable.");
|
return NotFound("A Target Currency must be set for this app in order to be loadable.");
|
||||||
}
|
}
|
||||||
if (settings.Enabled) return View(await _AppsHelper.GetCrowdfundInfo(appId));
|
if (settings.Enabled) return View(await _AppService.GetCrowdfundInfo(appId));
|
||||||
if(!isAdmin)
|
if(!isAdmin)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
return View(await _AppsHelper.GetCrowdfundInfo(appId));
|
return View(await _AppService.GetCrowdfundInfo(appId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -122,14 +122,14 @@ namespace BTCPayServer.Controllers
|
|||||||
[EnableCors(CorsPolicies.All)]
|
[EnableCors(CorsPolicies.All)]
|
||||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request)
|
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request)
|
||||||
{
|
{
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.Crowdfund, true);
|
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
|
||||||
if (app == null)
|
if (app == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
var settings = app.GetSettings<CrowdfundSettings>();
|
var settings = app.GetSettings<CrowdfundSettings>();
|
||||||
|
|
||||||
|
|
||||||
var isAdmin = await _AppsHelper.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
var isAdmin = await _AppService.GetAppDataIfOwner(GetUserId(), appId, AppType.Crowdfund) != null;
|
||||||
|
|
||||||
if (!settings.Enabled)
|
if (!settings.Enabled)
|
||||||
{
|
{
|
||||||
@@ -137,7 +137,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound("Crowdfund is not currently active");
|
return NotFound("Crowdfund is not currently active");
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = await _AppsHelper.GetCrowdfundInfo(appId);
|
var info = await _AppService.GetCrowdfundInfo(appId);
|
||||||
|
|
||||||
if (!isAdmin &&
|
if (!isAdmin &&
|
||||||
((settings.StartDate.HasValue && DateTime.Now < settings.StartDate) ||
|
((settings.StartDate.HasValue && DateTime.Now < settings.StartDate) ||
|
||||||
@@ -149,13 +149,13 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound("Crowdfund is not currently active");
|
return NotFound("Crowdfund is not currently active");
|
||||||
}
|
}
|
||||||
|
|
||||||
var store = await _AppsHelper.GetStore(app);
|
var store = await _AppService.GetStore(app);
|
||||||
var title = settings.Title;
|
var title = settings.Title;
|
||||||
var price = request.Amount;
|
var price = request.Amount;
|
||||||
ViewPointOfSaleViewModel.Item choice = null;
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
if (!string.IsNullOrEmpty(request.ChoiceKey))
|
||||||
{
|
{
|
||||||
var choices = _AppsHelper.Parse(settings.PerksTemplate, settings.TargetCurrency);
|
var choices = _AppService.Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||||
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
choice = choices.FirstOrDefault(c => c.Id == request.ChoiceKey);
|
||||||
if (choice == null)
|
if (choice == null)
|
||||||
return NotFound("Incorrect option provided");
|
return NotFound("Incorrect option provided");
|
||||||
@@ -185,7 +185,7 @@ namespace BTCPayServer.Controllers
|
|||||||
FullNotifications = true,
|
FullNotifications = true,
|
||||||
ExtendedNotifications = true,
|
ExtendedNotifications = true,
|
||||||
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl()
|
RedirectURL = request.RedirectUrl ?? Request.GetDisplayUrl()
|
||||||
}, store, HttpContext.Request.GetAbsoluteRoot(), new List<string> { AppsHelper.GetAppInternalTag(appId) });
|
}, store, HttpContext.Request.GetAbsoluteRoot(), new List<string> { AppService.GetAppInternalTag(appId) });
|
||||||
if (request.RedirectToCheckout)
|
if (request.RedirectToCheckout)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice",
|
return RedirectToAction(nameof(InvoiceController.Checkout), "Invoice",
|
||||||
@@ -217,7 +217,7 @@ namespace BTCPayServer.Controllers
|
|||||||
string choiceKey,
|
string choiceKey,
|
||||||
string posData = null)
|
string posData = null)
|
||||||
{
|
{
|
||||||
var app = await _AppsHelper.GetApp(appId, AppType.PointOfSale);
|
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
||||||
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
if (string.IsNullOrEmpty(choiceKey) && amount <= 0)
|
||||||
{
|
{
|
||||||
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
return RedirectToAction(nameof(ViewPointOfSale), new { appId = appId });
|
||||||
@@ -234,7 +234,7 @@ namespace BTCPayServer.Controllers
|
|||||||
ViewPointOfSaleViewModel.Item choice = null;
|
ViewPointOfSaleViewModel.Item choice = null;
|
||||||
if (!string.IsNullOrEmpty(choiceKey))
|
if (!string.IsNullOrEmpty(choiceKey))
|
||||||
{
|
{
|
||||||
var choices = _AppsHelper.Parse(settings.Template, settings.Currency);
|
var choices = _AppService.Parse(settings.Template, settings.Currency);
|
||||||
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
choice = choices.FirstOrDefault(c => c.Id == choiceKey);
|
||||||
if (choice == null)
|
if (choice == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@@ -250,7 +250,7 @@ namespace BTCPayServer.Controllers
|
|||||||
price = amount;
|
price = amount;
|
||||||
title = settings.Title;
|
title = settings.Title;
|
||||||
}
|
}
|
||||||
var store = await _AppsHelper.GetStore(app);
|
var store = await _AppService.GetStore(app);
|
||||||
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 NBitpayClient.Invoice()
|
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
|
||||||
{
|
{
|
||||||
@@ -274,365 +274,4 @@ namespace BTCPayServer.Controllers
|
|||||||
return _UserManager.GetUserId(User);
|
return _UserManager.GetUserId(User);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppsHelper
|
|
||||||
{
|
|
||||||
ApplicationDbContextFactory _ContextFactory;
|
|
||||||
private readonly InvoiceRepository _InvoiceRepository;
|
|
||||||
CurrencyNameTable _Currencies;
|
|
||||||
private readonly RateFetcher _RateFetcher;
|
|
||||||
private readonly HtmlSanitizer _HtmlSanitizer;
|
|
||||||
private readonly BTCPayNetworkProvider _Networks;
|
|
||||||
public CurrencyNameTable Currencies => _Currencies;
|
|
||||||
public AppsHelper(ApplicationDbContextFactory contextFactory,
|
|
||||||
InvoiceRepository invoiceRepository,
|
|
||||||
BTCPayNetworkProvider networks,
|
|
||||||
CurrencyNameTable currencies,
|
|
||||||
RateFetcher rateFetcher,
|
|
||||||
HtmlSanitizer htmlSanitizer)
|
|
||||||
{
|
|
||||||
_ContextFactory = contextFactory;
|
|
||||||
_InvoiceRepository = invoiceRepository;
|
|
||||||
_Currencies = currencies;
|
|
||||||
_RateFetcher = rateFetcher;
|
|
||||||
_HtmlSanitizer = htmlSanitizer;
|
|
||||||
_Networks = networks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId)
|
|
||||||
{
|
|
||||||
var app = await GetApp(appId, AppType.Crowdfund, true);
|
|
||||||
return await GetInfo(app);
|
|
||||||
}
|
|
||||||
private async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, string statusMessage = null)
|
|
||||||
{
|
|
||||||
var settings = appData.GetSettings<AppsController.CrowdfundSettings>();
|
|
||||||
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();
|
|
||||||
|
|
||||||
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_Networks);
|
|
||||||
|
|
||||||
var pendingPaymentStats = GetCurrentContributionAmountStats(pendingInvoices, !settings.UseInvoiceAmount);
|
|
||||||
var paymentStats = GetCurrentContributionAmountStats(completeInvoices, !settings.UseInvoiceAmount);
|
|
||||||
|
|
||||||
var currentAmount = await GetCurrentContributionAmount(
|
|
||||||
paymentStats,
|
|
||||||
settings.TargetCurrency, rateRules);
|
|
||||||
var currentPendingAmount = await GetCurrentContributionAmount(
|
|
||||||
pendingPaymentStats,
|
|
||||||
settings.TargetCurrency, rateRules);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var perkCount = invoices
|
|
||||||
.Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode))
|
|
||||||
.GroupBy(entity => entity.ProductInformation.ItemCode)
|
|
||||||
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
|
||||||
|
|
||||||
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
|
||||||
if (settings.SortPerksByPopularity)
|
|
||||||
{
|
|
||||||
var ordered = perkCount.OrderByDescending(pair => pair.Value);
|
|
||||||
var newPerksOrder = ordered
|
|
||||||
.Select(keyValuePair => perks.SingleOrDefault(item => item.Id == keyValuePair.Key))
|
|
||||||
.Where(matchingPerk => matchingPerk != null)
|
|
||||||
.ToList();
|
|
||||||
var remainingPerks = perks.Where(item => !newPerksOrder.Contains(item));
|
|
||||||
newPerksOrder.AddRange(remainingPerks);
|
|
||||||
perks = newPerksOrder.ToArray();
|
|
||||||
}
|
|
||||||
return new ViewCrowdfundViewModel()
|
|
||||||
{
|
|
||||||
Title = settings.Title,
|
|
||||||
Tagline = settings.Tagline,
|
|
||||||
Description = settings.Description,
|
|
||||||
CustomCSSLink = settings.CustomCSSLink,
|
|
||||||
MainImageUrl = settings.MainImageUrl,
|
|
||||||
EmbeddedCSS = settings.EmbeddedCSS,
|
|
||||||
StoreId = appData.StoreDataId,
|
|
||||||
AppId = appData.Id,
|
|
||||||
StartDate = settings.StartDate?.ToUniversalTime(),
|
|
||||||
EndDate = settings.EndDate?.ToUniversalTime(),
|
|
||||||
TargetAmount = settings.TargetAmount,
|
|
||||||
TargetCurrency = settings.TargetCurrency,
|
|
||||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
|
||||||
StatusMessage = statusMessage,
|
|
||||||
Perks = perks,
|
|
||||||
DisqusEnabled = settings.DisqusEnabled,
|
|
||||||
SoundsEnabled = settings.SoundsEnabled,
|
|
||||||
DisqusShortname = settings.DisqusShortname,
|
|
||||||
AnimationsEnabled = settings.AnimationsEnabled,
|
|
||||||
ResetEveryAmount = settings.ResetEveryAmount,
|
|
||||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
|
||||||
PerkCount = perkCount,
|
|
||||||
ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery),
|
|
||||||
CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true),
|
|
||||||
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
|
||||||
{
|
|
||||||
TotalContributors = invoices.Length,
|
|
||||||
CurrentPendingAmount = currentPendingAmount,
|
|
||||||
CurrentAmount = currentAmount,
|
|
||||||
ProgressPercentage = (currentAmount / settings.TargetAmount) * 100,
|
|
||||||
PendingProgressPercentage = (currentPendingAmount / settings.TargetAmount) * 100,
|
|
||||||
LastUpdated = DateTime.Now,
|
|
||||||
PaymentStats = paymentStats,
|
|
||||||
PendingPaymentStats = pendingPaymentStats,
|
|
||||||
LastResetDate = lastResetDate,
|
|
||||||
NextResetDate = nextResetDate
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetCrowdfundOrderId(string appId) => $"crowdfund-app_{appId}";
|
|
||||||
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
|
||||||
public static string[] GetAppInternalTags(IEnumerable<string> tags)
|
|
||||||
{
|
|
||||||
return tags == null ? Array.Empty<string>() : tags
|
|
||||||
.Where(t => t.StartsWith("APP#", StringComparison.InvariantCulture))
|
|
||||||
.Select(t => t.Substring("APP#".Length)).ToArray();
|
|
||||||
}
|
|
||||||
private async Task<InvoiceEntity[]> GetInvoicesForApp(AppData appData, DateTime? startDate = null)
|
|
||||||
{
|
|
||||||
var invoices = await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
|
||||||
{
|
|
||||||
StoreId = new[] { appData.StoreData.Id },
|
|
||||||
OrderId = appData.TagAllInvoices ? null : new[] { GetCrowdfundOrderId(appData.Id) },
|
|
||||||
Status = new string[]{
|
|
||||||
InvoiceState.ToString(InvoiceStatus.New),
|
|
||||||
InvoiceState.ToString(InvoiceStatus.Paid),
|
|
||||||
InvoiceState.ToString(InvoiceStatus.Confirmed),
|
|
||||||
InvoiceState.ToString(InvoiceStatus.Complete)},
|
|
||||||
StartDate = startDate
|
|
||||||
});
|
|
||||||
|
|
||||||
// Old invoices may have invoices which were not tagged
|
|
||||||
invoices = invoices.Where(inv => inv.Version < InvoiceEntity.InternalTagSupport_Version ||
|
|
||||||
inv.InternalTags.Contains(GetAppInternalTag(appData.Id))).ToArray();
|
|
||||||
return invoices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<StoreData[]> GetOwnedStores(string userId)
|
|
||||||
{
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
return await ctx.UserStore
|
|
||||||
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
|
|
||||||
.Select(u => u.StoreData)
|
|
||||||
.ToArrayAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> DeleteApp(AppData appData)
|
|
||||||
{
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
ctx.Apps.Add(appData);
|
|
||||||
ctx.Entry<AppData>(appData).State = EntityState.Deleted;
|
|
||||||
return await ctx.SaveChangesAsync() == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string userId, bool allowNoUser = false)
|
|
||||||
{
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
return await ctx.UserStore
|
|
||||||
.Where(us => (allowNoUser && string.IsNullOrEmpty(userId) ) || us.ApplicationUserId == userId)
|
|
||||||
.Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId,
|
|
||||||
(us, app) =>
|
|
||||||
new ListAppsViewModel.ListAppViewModel()
|
|
||||||
{
|
|
||||||
IsOwner = us.Role == StoreRoles.Owner,
|
|
||||||
StoreId = us.StoreDataId,
|
|
||||||
StoreName = us.StoreData.StoreName,
|
|
||||||
AppName = app.Name,
|
|
||||||
AppType = app.AppType,
|
|
||||||
Id = app.Id
|
|
||||||
})
|
|
||||||
.ToArrayAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
|
||||||
{
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<StoreData> GetStore(AppData app)
|
|
||||||
{
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
return await ctx.Stores.FirstOrDefaultAsync(s => s.Id == app.StoreDataId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ViewPointOfSaleViewModel.Item[] Parse(string template, string currency)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(template))
|
|
||||||
return Array.Empty<ViewPointOfSaleViewModel.Item>();
|
|
||||||
var input = new StringReader(template);
|
|
||||||
YamlStream stream = new YamlStream();
|
|
||||||
stream.Load(input);
|
|
||||||
var root = (YamlMappingNode)stream.Documents[0].RootNode;
|
|
||||||
return root
|
|
||||||
.Children
|
|
||||||
.Select(kv => new PosHolder { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlMappingNode })
|
|
||||||
.Where(kv => kv.Value != null)
|
|
||||||
.Select(c => new ViewPointOfSaleViewModel.Item()
|
|
||||||
{
|
|
||||||
Description = _HtmlSanitizer.Sanitize(c.GetDetailString("description")),
|
|
||||||
Id = c.Key,
|
|
||||||
Image = _HtmlSanitizer.Sanitize(c.GetDetailString("image")),
|
|
||||||
Title = _HtmlSanitizer.Sanitize(c.GetDetailString("title") ?? c.Key),
|
|
||||||
Price = c.GetDetail("price")
|
|
||||||
.Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice()
|
|
||||||
{
|
|
||||||
Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture),
|
|
||||||
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
|
||||||
}).Single(),
|
|
||||||
Custom = c.GetDetailString("custom") == "true"
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<decimal> GetCurrentContributionAmount(Dictionary<string, decimal> stats, string primaryCurrency, RateRules rateRules)
|
|
||||||
{
|
|
||||||
var result = new List<decimal>();
|
|
||||||
|
|
||||||
var ratesTask = _RateFetcher.FetchRates(
|
|
||||||
stats.Keys
|
|
||||||
.Select((x) => new CurrencyPair(primaryCurrency, PaymentMethodId.Parse(x).CryptoCode))
|
|
||||||
.Distinct()
|
|
||||||
.ToHashSet(),
|
|
||||||
rateRules).Select(async rateTask =>
|
|
||||||
{
|
|
||||||
var (key, value) = rateTask;
|
|
||||||
var tResult = await value;
|
|
||||||
var rate = tResult.BidAsk?.Bid;
|
|
||||||
if (rate == null) return;
|
|
||||||
|
|
||||||
foreach (var stat in stats)
|
|
||||||
{
|
|
||||||
if (string.Equals(PaymentMethodId.Parse(stat.Key).CryptoCode, key.Right,
|
|
||||||
StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
result.Add((1m / rate.Value) * stat.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(ratesTask);
|
|
||||||
|
|
||||||
return result.Sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<string, decimal> GetCurrentContributionAmountStats(InvoiceEntity[] invoices, bool usePaymentData = true)
|
|
||||||
{
|
|
||||||
if(usePaymentData){
|
|
||||||
var payments = invoices.SelectMany(entity => entity.GetPayments());
|
|
||||||
|
|
||||||
var groupedByMethod = payments.GroupBy(entity => entity.GetPaymentMethodId());
|
|
||||||
|
|
||||||
return groupedByMethod.ToDictionary(entities => entities.Key.ToString(),
|
|
||||||
entities => entities.Sum(entity => entity.GetCryptoPaymentData().GetValue()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return invoices
|
|
||||||
.GroupBy(entity => entity.ProductInformation.Currency)
|
|
||||||
.ToDictionary(
|
|
||||||
entities => entities.Key,
|
|
||||||
entities => entities.Sum(entity => entity.ProductInformation.Price));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PosHolder
|
|
||||||
{
|
|
||||||
public string Key { get; set; }
|
|
||||||
public YamlMappingNode Value { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<PosScalar> GetDetail(string field)
|
|
||||||
{
|
|
||||||
var res = Value.Children
|
|
||||||
.Where(kv => kv.Value != null)
|
|
||||||
.Select(kv => new PosScalar { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode })
|
|
||||||
.Where(cc => cc.Key == field);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetDetailString(string field)
|
|
||||||
{
|
|
||||||
|
|
||||||
return GetDetail(field).FirstOrDefault()?.Value?.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private class PosScalar
|
|
||||||
{
|
|
||||||
public string Key { get; set; }
|
|
||||||
public YamlScalarNode Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AppData> GetAppDataIfOwner(string userId, string appId, AppType? type = null)
|
|
||||||
{
|
|
||||||
if (userId == null || appId == null)
|
|
||||||
return null;
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
var app = await ctx.UserStore
|
|
||||||
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
|
|
||||||
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (app == null)
|
|
||||||
return null;
|
|
||||||
if (type != null && type.Value.ToString() != app.AppType)
|
|
||||||
return null;
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using BTCPayServer.Models;
|
|||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Rating;
|
using BTCPayServer.Rating;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Security;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
using BTCPayServer.Services.Invoices;
|
using BTCPayServer.Services.Invoices;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
@@ -179,7 +180,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
foreach (var app in await getAppsTaggingStore)
|
foreach (var app in await getAppsTaggingStore)
|
||||||
{
|
{
|
||||||
entity.InternalTags.Add(AppsHelper.GetAppInternalTag(app.Id));
|
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, logs, _NetworkProvider);
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, logs, _NetworkProvider);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace BTCPayServer.Crowdfund
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach(var appId in AppsHelper.GetAppInternalTags(evt.Invoice.InternalTags))
|
foreach(var appId in AppService.GetAppInternalTags(evt.Invoice.InternalTags))
|
||||||
await NotifyClients(appId, evt, cancellationToken);
|
await NotifyClients(appId, evt, cancellationToken);
|
||||||
}
|
}
|
||||||
catch when (cancellationToken.IsCancellationRequested)
|
catch when (cancellationToken.IsCancellationRequested)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -106,7 +107,7 @@ namespace BTCPayServer.Hosting
|
|||||||
return opts.NetworkProvider;
|
return opts.NetworkProvider;
|
||||||
});
|
});
|
||||||
|
|
||||||
services.TryAddSingleton<AppsHelper>();
|
services.TryAddSingleton<AppService>();
|
||||||
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
394
BTCPayServer/Services/Apps/AppService.cs
Normal file
394
BTCPayServer/Services/Apps/AppService.cs
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Crowdfund;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Models;
|
||||||
|
using BTCPayServer.Models.AppViewModels;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using BTCPayServer.Rating;
|
||||||
|
using BTCPayServer.Security;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using Ganss.XSS;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NBitpayClient;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
using static BTCPayServer.Controllers.AppsController;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Apps
|
||||||
|
{
|
||||||
|
public class AppService
|
||||||
|
{
|
||||||
|
ApplicationDbContextFactory _ContextFactory;
|
||||||
|
private readonly InvoiceRepository _InvoiceRepository;
|
||||||
|
CurrencyNameTable _Currencies;
|
||||||
|
private readonly RateFetcher _RateFetcher;
|
||||||
|
private readonly HtmlSanitizer _HtmlSanitizer;
|
||||||
|
private readonly BTCPayNetworkProvider _Networks;
|
||||||
|
public CurrencyNameTable Currencies => _Currencies;
|
||||||
|
public AppService(ApplicationDbContextFactory contextFactory,
|
||||||
|
InvoiceRepository invoiceRepository,
|
||||||
|
BTCPayNetworkProvider networks,
|
||||||
|
CurrencyNameTable currencies,
|
||||||
|
RateFetcher rateFetcher,
|
||||||
|
HtmlSanitizer htmlSanitizer)
|
||||||
|
{
|
||||||
|
_ContextFactory = contextFactory;
|
||||||
|
_InvoiceRepository = invoiceRepository;
|
||||||
|
_Currencies = currencies;
|
||||||
|
_RateFetcher = rateFetcher;
|
||||||
|
_HtmlSanitizer = htmlSanitizer;
|
||||||
|
_Networks = networks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ViewCrowdfundViewModel> GetCrowdfundInfo(string appId)
|
||||||
|
{
|
||||||
|
var app = await GetApp(appId, AppType.Crowdfund, true);
|
||||||
|
return await GetInfo(app);
|
||||||
|
}
|
||||||
|
private async Task<ViewCrowdfundViewModel> GetInfo(AppData appData, string statusMessage = null)
|
||||||
|
{
|
||||||
|
var settings = appData.GetSettings<CrowdfundSettings>();
|
||||||
|
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();
|
||||||
|
|
||||||
|
var rateRules = appData.StoreData.GetStoreBlob().GetRateRules(_Networks);
|
||||||
|
|
||||||
|
var pendingPaymentStats = GetCurrentContributionAmountStats(pendingInvoices, !settings.UseInvoiceAmount);
|
||||||
|
var paymentStats = GetCurrentContributionAmountStats(completeInvoices, !settings.UseInvoiceAmount);
|
||||||
|
|
||||||
|
var currentAmount = await GetCurrentContributionAmount(
|
||||||
|
paymentStats,
|
||||||
|
settings.TargetCurrency, rateRules);
|
||||||
|
var currentPendingAmount = await GetCurrentContributionAmount(
|
||||||
|
pendingPaymentStats,
|
||||||
|
settings.TargetCurrency, rateRules);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var perkCount = invoices
|
||||||
|
.Where(entity => !string.IsNullOrEmpty(entity.ProductInformation.ItemCode))
|
||||||
|
.GroupBy(entity => entity.ProductInformation.ItemCode)
|
||||||
|
.ToDictionary(entities => entities.Key, entities => entities.Count());
|
||||||
|
|
||||||
|
var perks = Parse(settings.PerksTemplate, settings.TargetCurrency);
|
||||||
|
if (settings.SortPerksByPopularity)
|
||||||
|
{
|
||||||
|
var ordered = perkCount.OrderByDescending(pair => pair.Value);
|
||||||
|
var newPerksOrder = ordered
|
||||||
|
.Select(keyValuePair => perks.SingleOrDefault(item => item.Id == keyValuePair.Key))
|
||||||
|
.Where(matchingPerk => matchingPerk != null)
|
||||||
|
.ToList();
|
||||||
|
var remainingPerks = perks.Where(item => !newPerksOrder.Contains(item));
|
||||||
|
newPerksOrder.AddRange(remainingPerks);
|
||||||
|
perks = newPerksOrder.ToArray();
|
||||||
|
}
|
||||||
|
return new ViewCrowdfundViewModel()
|
||||||
|
{
|
||||||
|
Title = settings.Title,
|
||||||
|
Tagline = settings.Tagline,
|
||||||
|
Description = settings.Description,
|
||||||
|
CustomCSSLink = settings.CustomCSSLink,
|
||||||
|
MainImageUrl = settings.MainImageUrl,
|
||||||
|
EmbeddedCSS = settings.EmbeddedCSS,
|
||||||
|
StoreId = appData.StoreDataId,
|
||||||
|
AppId = appData.Id,
|
||||||
|
StartDate = settings.StartDate?.ToUniversalTime(),
|
||||||
|
EndDate = settings.EndDate?.ToUniversalTime(),
|
||||||
|
TargetAmount = settings.TargetAmount,
|
||||||
|
TargetCurrency = settings.TargetCurrency,
|
||||||
|
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||||
|
StatusMessage = statusMessage,
|
||||||
|
Perks = perks,
|
||||||
|
DisqusEnabled = settings.DisqusEnabled,
|
||||||
|
SoundsEnabled = settings.SoundsEnabled,
|
||||||
|
DisqusShortname = settings.DisqusShortname,
|
||||||
|
AnimationsEnabled = settings.AnimationsEnabled,
|
||||||
|
ResetEveryAmount = settings.ResetEveryAmount,
|
||||||
|
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||||
|
PerkCount = perkCount,
|
||||||
|
ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery),
|
||||||
|
CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true),
|
||||||
|
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
||||||
|
{
|
||||||
|
TotalContributors = invoices.Length,
|
||||||
|
CurrentPendingAmount = currentPendingAmount,
|
||||||
|
CurrentAmount = currentAmount,
|
||||||
|
ProgressPercentage = (currentAmount / settings.TargetAmount) * 100,
|
||||||
|
PendingProgressPercentage = (currentPendingAmount / settings.TargetAmount) * 100,
|
||||||
|
LastUpdated = DateTime.Now,
|
||||||
|
PaymentStats = paymentStats,
|
||||||
|
PendingPaymentStats = pendingPaymentStats,
|
||||||
|
LastResetDate = lastResetDate,
|
||||||
|
NextResetDate = nextResetDate
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCrowdfundOrderId(string appId) => $"crowdfund-app_{appId}";
|
||||||
|
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
||||||
|
public static string[] GetAppInternalTags(IEnumerable<string> tags)
|
||||||
|
{
|
||||||
|
return tags == null ? Array.Empty<string>() : tags
|
||||||
|
.Where(t => t.StartsWith("APP#", StringComparison.InvariantCulture))
|
||||||
|
.Select(t => t.Substring("APP#".Length)).ToArray();
|
||||||
|
}
|
||||||
|
private async Task<InvoiceEntity[]> GetInvoicesForApp(AppData appData, DateTime? startDate = null)
|
||||||
|
{
|
||||||
|
var invoices = await _InvoiceRepository.GetInvoices(new InvoiceQuery()
|
||||||
|
{
|
||||||
|
StoreId = new[] { appData.StoreData.Id },
|
||||||
|
OrderId = appData.TagAllInvoices ? null : new[] { GetCrowdfundOrderId(appData.Id) },
|
||||||
|
Status = new string[]{
|
||||||
|
InvoiceState.ToString(InvoiceStatus.New),
|
||||||
|
InvoiceState.ToString(InvoiceStatus.Paid),
|
||||||
|
InvoiceState.ToString(InvoiceStatus.Confirmed),
|
||||||
|
InvoiceState.ToString(InvoiceStatus.Complete)},
|
||||||
|
StartDate = startDate
|
||||||
|
});
|
||||||
|
|
||||||
|
// Old invoices may have invoices which were not tagged
|
||||||
|
invoices = invoices.Where(inv => inv.Version < InvoiceEntity.InternalTagSupport_Version ||
|
||||||
|
inv.InternalTags.Contains(GetAppInternalTag(appData.Id))).ToArray();
|
||||||
|
return invoices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StoreData[]> GetOwnedStores(string userId)
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
return await ctx.UserStore
|
||||||
|
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
|
||||||
|
.Select(u => u.StoreData)
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteApp(AppData appData)
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
ctx.Apps.Add(appData);
|
||||||
|
ctx.Entry<AppData>(appData).State = EntityState.Deleted;
|
||||||
|
return await ctx.SaveChangesAsync() == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string userId, bool allowNoUser = false)
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
return await ctx.UserStore
|
||||||
|
.Where(us => (allowNoUser && string.IsNullOrEmpty(userId)) || us.ApplicationUserId == userId)
|
||||||
|
.Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId,
|
||||||
|
(us, app) =>
|
||||||
|
new ListAppsViewModel.ListAppViewModel()
|
||||||
|
{
|
||||||
|
IsOwner = us.Role == StoreRoles.Owner,
|
||||||
|
StoreId = us.StoreDataId,
|
||||||
|
StoreName = us.StoreData.StoreName,
|
||||||
|
AppName = app.Name,
|
||||||
|
AppType = app.AppType,
|
||||||
|
Id = app.Id
|
||||||
|
})
|
||||||
|
.ToArrayAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StoreData> GetStore(AppData app)
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
return await ctx.Stores.FirstOrDefaultAsync(s => s.Id == app.StoreDataId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ViewPointOfSaleViewModel.Item[] Parse(string template, string currency)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
|
return Array.Empty<ViewPointOfSaleViewModel.Item>();
|
||||||
|
var input = new StringReader(template);
|
||||||
|
YamlStream stream = new YamlStream();
|
||||||
|
stream.Load(input);
|
||||||
|
var root = (YamlMappingNode)stream.Documents[0].RootNode;
|
||||||
|
return root
|
||||||
|
.Children
|
||||||
|
.Select(kv => new PosHolder { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlMappingNode })
|
||||||
|
.Where(kv => kv.Value != null)
|
||||||
|
.Select(c => new ViewPointOfSaleViewModel.Item()
|
||||||
|
{
|
||||||
|
Description = _HtmlSanitizer.Sanitize(c.GetDetailString("description")),
|
||||||
|
Id = c.Key,
|
||||||
|
Image = _HtmlSanitizer.Sanitize(c.GetDetailString("image")),
|
||||||
|
Title = _HtmlSanitizer.Sanitize(c.GetDetailString("title") ?? c.Key),
|
||||||
|
Price = c.GetDetail("price")
|
||||||
|
.Select(cc => new ViewPointOfSaleViewModel.Item.ItemPrice()
|
||||||
|
{
|
||||||
|
Value = decimal.Parse(cc.Value.Value, CultureInfo.InvariantCulture),
|
||||||
|
Formatted = Currencies.FormatCurrency(cc.Value.Value, currency)
|
||||||
|
}).Single(),
|
||||||
|
Custom = c.GetDetailString("custom") == "true"
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetCurrentContributionAmount(Dictionary<string, decimal> stats, string primaryCurrency, RateRules rateRules)
|
||||||
|
{
|
||||||
|
var result = new List<decimal>();
|
||||||
|
|
||||||
|
var ratesTask = _RateFetcher.FetchRates(
|
||||||
|
stats.Keys
|
||||||
|
.Select((x) => new CurrencyPair(primaryCurrency, PaymentMethodId.Parse(x).CryptoCode))
|
||||||
|
.Distinct()
|
||||||
|
.ToHashSet(),
|
||||||
|
rateRules).Select(async rateTask =>
|
||||||
|
{
|
||||||
|
var (key, value) = rateTask;
|
||||||
|
var tResult = await value;
|
||||||
|
var rate = tResult.BidAsk?.Bid;
|
||||||
|
if (rate == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var stat in stats)
|
||||||
|
{
|
||||||
|
if (string.Equals(PaymentMethodId.Parse(stat.Key).CryptoCode, key.Right,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Add((1m / rate.Value) * stat.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(ratesTask);
|
||||||
|
|
||||||
|
return result.Sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, decimal> GetCurrentContributionAmountStats(InvoiceEntity[] invoices, bool usePaymentData = true)
|
||||||
|
{
|
||||||
|
if (usePaymentData)
|
||||||
|
{
|
||||||
|
var payments = invoices.SelectMany(entity => entity.GetPayments());
|
||||||
|
|
||||||
|
var groupedByMethod = payments.GroupBy(entity => entity.GetPaymentMethodId());
|
||||||
|
|
||||||
|
return groupedByMethod.ToDictionary(entities => entities.Key.ToString(),
|
||||||
|
entities => entities.Sum(entity => entity.GetCryptoPaymentData().GetValue()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return invoices
|
||||||
|
.GroupBy(entity => entity.ProductInformation.Currency)
|
||||||
|
.ToDictionary(
|
||||||
|
entities => entities.Key,
|
||||||
|
entities => entities.Sum(entity => entity.ProductInformation.Price));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PosHolder
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public YamlMappingNode Value { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<PosScalar> GetDetail(string field)
|
||||||
|
{
|
||||||
|
var res = Value.Children
|
||||||
|
.Where(kv => kv.Value != null)
|
||||||
|
.Select(kv => new PosScalar { Key = (kv.Key as YamlScalarNode)?.Value, Value = kv.Value as YamlScalarNode })
|
||||||
|
.Where(cc => cc.Key == field);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDetailString(string field)
|
||||||
|
{
|
||||||
|
|
||||||
|
return GetDetail(field).FirstOrDefault()?.Value?.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private class PosScalar
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public YamlScalarNode Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AppData> GetAppDataIfOwner(string userId, string appId, AppType? type = null)
|
||||||
|
{
|
||||||
|
if (userId == null || appId == null)
|
||||||
|
return null;
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var app = await ctx.UserStore
|
||||||
|
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
|
||||||
|
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (app == null)
|
||||||
|
return null;
|
||||||
|
if (type != null && type.Value.ToString() != app.AppType)
|
||||||
|
return null;
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user