2020-06-28 21:44:35 -05:00
|
|
|
using System;
|
2019-02-19 13:04:58 +09:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Globalization;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading.Tasks;
|
2020-07-24 09:40:37 +02:00
|
|
|
using BTCPayServer.Client.Models;
|
2019-02-19 13:04:58 +09:00
|
|
|
using BTCPayServer.Data;
|
|
|
|
using BTCPayServer.Models.AppViewModels;
|
|
|
|
using BTCPayServer.Payments;
|
|
|
|
using BTCPayServer.Services.Invoices;
|
|
|
|
using BTCPayServer.Services.Rates;
|
2019-05-30 07:02:52 +00:00
|
|
|
using BTCPayServer.Services.Stores;
|
2019-09-02 15:37:52 +02:00
|
|
|
using ExchangeSharp;
|
2019-02-19 13:04:58 +09:00
|
|
|
using Ganss.XSS;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2019-09-02 15:37:52 +02:00
|
|
|
using NBitcoin;
|
|
|
|
using NBitcoin.DataEncoders;
|
|
|
|
using Newtonsoft.Json.Linq;
|
2020-03-10 11:20:05 +01:00
|
|
|
using NUglify.Helpers;
|
2019-02-19 13:04:58 +09:00
|
|
|
using YamlDotNet.RepresentationModel;
|
2019-09-02 15:37:52 +02:00
|
|
|
using YamlDotNet.Serialization;
|
2019-03-05 13:54:34 +09:00
|
|
|
using static BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel;
|
2020-07-24 09:40:37 +02:00
|
|
|
using StoreData = BTCPayServer.Data.StoreData;
|
2019-02-19 13:04:58 +09:00
|
|
|
|
|
|
|
namespace BTCPayServer.Services.Apps
|
|
|
|
{
|
|
|
|
public class AppService
|
|
|
|
{
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly ApplicationDbContextFactory _ContextFactory;
|
2019-02-19 13:04:58 +09:00
|
|
|
private readonly InvoiceRepository _InvoiceRepository;
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly CurrencyNameTable _Currencies;
|
2019-05-30 07:02:52 +00:00
|
|
|
private readonly StoreRepository _storeRepository;
|
2019-02-19 13:04:58 +09:00
|
|
|
private readonly HtmlSanitizer _HtmlSanitizer;
|
|
|
|
public CurrencyNameTable Currencies => _Currencies;
|
|
|
|
public AppService(ApplicationDbContextFactory contextFactory,
|
|
|
|
InvoiceRepository invoiceRepository,
|
|
|
|
CurrencyNameTable currencies,
|
2019-05-30 07:02:52 +00:00
|
|
|
StoreRepository storeRepository,
|
2019-02-19 13:04:58 +09:00
|
|
|
HtmlSanitizer htmlSanitizer)
|
|
|
|
{
|
|
|
|
_ContextFactory = contextFactory;
|
|
|
|
_InvoiceRepository = invoiceRepository;
|
|
|
|
_Currencies = currencies;
|
2019-05-30 07:02:52 +00:00
|
|
|
_storeRepository = storeRepository;
|
2019-02-19 13:04:58 +09:00
|
|
|
_HtmlSanitizer = htmlSanitizer;
|
|
|
|
}
|
|
|
|
|
2019-02-19 16:01:28 +09:00
|
|
|
public async Task<object> GetAppInfo(string appId)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
|
|
|
var app = await GetApp(appId, AppType.Crowdfund, true);
|
2019-04-08 16:02:53 +02:00
|
|
|
if (app != null)
|
|
|
|
{
|
|
|
|
return await GetInfo(app);
|
|
|
|
}
|
|
|
|
return null;
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
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);
|
2019-02-19 16:01:28 +09:00
|
|
|
var completeInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed).ToArray();
|
|
|
|
var pendingInvoices = invoices.Where(entity => !(entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed)).ToArray();
|
2019-07-13 22:32:54 +09:00
|
|
|
var paidInvoices = invoices.Where(entity => entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed || entity.Status == InvoiceStatus.Paid).ToArray();
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2019-03-05 14:26:27 +09:00
|
|
|
var pendingPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount);
|
|
|
|
var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount);
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2019-07-15 17:01:12 +09:00
|
|
|
var perkCount = paidInvoices
|
2020-08-25 14:33:00 +09:00
|
|
|
.Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode))
|
|
|
|
.GroupBy(entity => entity.Metadata.ItemCode)
|
2019-02-19 13:04:58 +09:00
|
|
|
.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,
|
|
|
|
Perks = perks,
|
2019-03-03 17:06:11 -06:00
|
|
|
Enabled = settings.Enabled,
|
2019-02-19 13:04:58 +09:00
|
|
|
DisqusEnabled = settings.DisqusEnabled,
|
|
|
|
SoundsEnabled = settings.SoundsEnabled,
|
|
|
|
DisqusShortname = settings.DisqusShortname,
|
|
|
|
AnimationsEnabled = settings.AnimationsEnabled,
|
|
|
|
ResetEveryAmount = settings.ResetEveryAmount,
|
2019-04-13 13:22:19 +02:00
|
|
|
ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery),
|
2019-02-19 13:04:58 +09:00
|
|
|
DisplayPerksRanking = settings.DisplayPerksRanking,
|
|
|
|
PerkCount = perkCount,
|
2019-02-19 13:18:30 +09:00
|
|
|
NeverReset = settings.ResetEvery == CrowdfundResetEvery.Never,
|
2019-03-07 06:25:09 +01:00
|
|
|
Sounds = settings.Sounds,
|
|
|
|
AnimationColors = settings.AnimationColors,
|
2019-02-19 13:04:58 +09:00
|
|
|
CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true),
|
2020-02-28 12:51:15 +01:00
|
|
|
CurrencyDataPayments = currentPayments.Select(pair => pair.Key)
|
2020-03-10 11:20:05 +01:00
|
|
|
.Concat(pendingPayments.Select(pair => pair.Key))
|
2020-02-28 12:51:15 +01:00
|
|
|
.Select(id => _Currencies.GetCurrencyData(id.CryptoCode, true))
|
2020-03-10 11:20:05 +01:00
|
|
|
.DistinctBy(data => data.Code)
|
2020-02-28 12:51:15 +01:00
|
|
|
.ToDictionary(data => data.Code, data => data),
|
2019-02-19 13:04:58 +09:00
|
|
|
Info = new ViewCrowdfundViewModel.CrowdfundInfo()
|
|
|
|
{
|
2019-07-13 22:32:54 +09:00
|
|
|
TotalContributors = paidInvoices.Length,
|
2019-03-05 13:54:34 +09:00
|
|
|
ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100,
|
|
|
|
PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100,
|
2019-02-19 13:04:58 +09:00
|
|
|
LastUpdated = DateTime.Now,
|
2019-03-05 13:54:34 +09:00
|
|
|
PaymentStats = currentPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value),
|
|
|
|
PendingPaymentStats = pendingPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value),
|
2019-02-19 13:04:58 +09:00
|
|
|
LastResetDate = lastResetDate,
|
2019-03-05 14:06:40 +09:00
|
|
|
NextResetDate = nextResetDate,
|
|
|
|
CurrentPendingAmount = pendingPayments.TotalCurrency,
|
|
|
|
CurrentAmount = currentPayments.TotalCurrency
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public static string GetCrowdfundOrderId(string appId) => $"crowdfund-app_{appId}";
|
|
|
|
public static string GetAppInternalTag(string appId) => $"APP#{appId}";
|
2019-02-25 16:15:45 +09:00
|
|
|
public static string[] GetAppInternalTags(InvoiceEntity invoice)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2019-02-25 16:15:45 +09:00
|
|
|
return invoice.GetInternalTags("APP#");
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
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
|
2020-08-04 13:40:00 +02:00
|
|
|
invoices = invoices.Where(inv => appData.TagAllInvoices || inv.Version < InvoiceEntity.InternalTagSupport_Version ||
|
2019-02-19 13:04:58 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-15 10:51:57 +01:00
|
|
|
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string userId, bool allowNoUser = false, string storeId = null)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
return await ctx.UserStore
|
2020-03-15 10:51:57 +01:00
|
|
|
.Where(us =>
|
|
|
|
((allowNoUser && string.IsNullOrEmpty(userId)) || us.ApplicationUserId == userId) &&
|
|
|
|
(storeId == null || us.StoreDataId == storeId))
|
2019-02-19 13:04:58 +09:00
|
|
|
.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();
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 17:55:27 +09:00
|
|
|
|
2019-07-09 11:20:38 +02:00
|
|
|
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false)
|
|
|
|
{
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
var query = ctx.Apps
|
|
|
|
.Where(us => appIds.Contains(us.Id));
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2019-07-09 11:20:38 +02:00
|
|
|
if (includeStore)
|
|
|
|
{
|
|
|
|
query = query.Include(data => data.StoreData);
|
|
|
|
}
|
|
|
|
return await query.ToListAsync();
|
|
|
|
}
|
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2019-04-12 14:43:07 +09:00
|
|
|
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
var query = ctx.Apps
|
2019-04-12 14:43:07 +09:00
|
|
|
.Where(us => us.Id == appId &&
|
|
|
|
us.AppType == appType.ToString());
|
2019-02-19 13:04:58 +09:00
|
|
|
|
|
|
|
if (includeStore)
|
|
|
|
{
|
|
|
|
query = query.Include(data => data.StoreData);
|
|
|
|
}
|
|
|
|
return await query.FirstOrDefaultAsync();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 07:02:52 +00:00
|
|
|
public Task<StoreData> GetStore(AppData app)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2019-05-30 07:02:52 +00:00
|
|
|
return _storeRepository.FindStore(app.StoreDataId);
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
|
2019-09-02 15:37:52 +02:00
|
|
|
public string SerializeTemplate(ViewPointOfSaleViewModel.Item[] items)
|
|
|
|
{
|
|
|
|
var mappingNode = new YamlMappingNode();
|
|
|
|
foreach (var item in items)
|
|
|
|
{
|
|
|
|
var itemNode = new YamlMappingNode();
|
|
|
|
itemNode.Add("title", new YamlScalarNode(item.Title));
|
|
|
|
itemNode.Add("price", new YamlScalarNode(item.Price.Value.ToStringInvariant()));
|
|
|
|
if (!string.IsNullOrEmpty(item.Description))
|
|
|
|
{
|
|
|
|
itemNode.Add("description", new YamlScalarNode(item.Description));
|
|
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(item.Image))
|
|
|
|
{
|
|
|
|
itemNode.Add("image", new YamlScalarNode(item.Image));
|
|
|
|
}
|
|
|
|
itemNode.Add("custom", new YamlScalarNode(item.Custom.ToStringLowerInvariant()));
|
|
|
|
if (item.Inventory.HasValue)
|
|
|
|
{
|
|
|
|
itemNode.Add("inventory", new YamlScalarNode(item.Inventory.ToString()));
|
|
|
|
}
|
|
|
|
mappingNode.Add(item.Id, itemNode);
|
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
|
2019-09-02 15:37:52 +02:00
|
|
|
var serializer = new SerializerBuilder().Build();
|
|
|
|
return serializer.Serialize(mappingNode);
|
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
public ViewPointOfSaleViewModel.Item[] Parse(string template, string currency)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(template))
|
|
|
|
return Array.Empty<ViewPointOfSaleViewModel.Item>();
|
2020-01-12 15:32:26 +09:00
|
|
|
using var input = new StringReader(template);
|
2019-02-19 13:04:58 +09:00
|
|
|
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()
|
|
|
|
{
|
2019-08-10 14:05:11 +09:00
|
|
|
Description = c.GetDetailString("description"),
|
2019-02-19 13:04:58 +09:00
|
|
|
Id = c.Key,
|
2019-08-10 14:05:11 +09:00
|
|
|
Image = c.GetDetailString("image"),
|
|
|
|
Title = c.GetDetailString("title") ?? c.Key,
|
2019-02-19 13:04:58 +09:00
|
|
|
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(),
|
2019-09-02 15:37:52 +02:00
|
|
|
Custom = c.GetDetailString("custom") == "true",
|
2020-06-28 17:55:27 +09:00
|
|
|
Inventory = string.IsNullOrEmpty(c.GetDetailString("inventory")) ? (int?)null : int.Parse(c.GetDetailString("inventory"), CultureInfo.InvariantCulture),
|
2020-05-19 20:54:24 +02:00
|
|
|
PaymentMethods = c.GetDetailStringList("payment_methods")
|
2020-06-28 17:55:27 +09:00
|
|
|
|
2019-02-19 13:04:58 +09:00
|
|
|
})
|
|
|
|
.ToArray();
|
|
|
|
}
|
|
|
|
|
2019-03-05 14:26:27 +09:00
|
|
|
public Contributions GetContributionsByPaymentMethodId(string currency, InvoiceEntity[] invoices, bool softcap)
|
2019-02-19 13:04:58 +09:00
|
|
|
{
|
2019-03-05 13:54:34 +09:00
|
|
|
var contributions = invoices
|
2020-08-25 14:33:00 +09:00
|
|
|
.Where(p => p.Currency.Equals(currency, StringComparison.OrdinalIgnoreCase))
|
2019-02-19 16:01:28 +09:00
|
|
|
.SelectMany(p =>
|
|
|
|
{
|
2019-03-05 13:54:34 +09:00
|
|
|
var contribution = new Contribution();
|
2020-08-25 14:33:00 +09:00
|
|
|
contribution.PaymentMethodId = new PaymentMethodId(p.Currency, PaymentTypes.BTCLike);
|
|
|
|
contribution.CurrencyValue = p.Price;
|
2019-03-05 13:54:34 +09:00
|
|
|
contribution.Value = contribution.CurrencyValue;
|
|
|
|
|
2019-02-19 16:01:28 +09:00
|
|
|
// For hardcap, we count newly created invoices as part of the contributions
|
|
|
|
if (!softcap && p.Status == InvoiceStatus.New)
|
2019-03-05 13:54:34 +09:00
|
|
|
return new[] { contribution };
|
2019-02-19 16:01:28 +09:00
|
|
|
|
|
|
|
// If the user get a donation via other mean, he can register an invoice manually for such amount
|
|
|
|
// then mark the invoice as complete
|
|
|
|
var payments = p.GetPayments();
|
2019-02-19 16:15:14 +09:00
|
|
|
if (payments.Count == 0 &&
|
2019-02-19 16:01:28 +09:00
|
|
|
p.ExceptionStatus == InvoiceExceptionStatus.Marked &&
|
|
|
|
p.Status == InvoiceStatus.Complete)
|
2019-03-05 13:54:34 +09:00
|
|
|
return new[] { contribution };
|
|
|
|
|
|
|
|
contribution.CurrencyValue = 0m;
|
|
|
|
contribution.Value = 0m;
|
2019-02-19 16:01:28 +09:00
|
|
|
|
|
|
|
// If an invoice has been marked invalid, remove the contribution
|
|
|
|
if (p.ExceptionStatus == InvoiceExceptionStatus.Marked &&
|
|
|
|
p.Status == InvoiceStatus.Invalid)
|
2019-03-05 13:54:34 +09:00
|
|
|
return new[] { contribution };
|
|
|
|
|
2019-02-19 16:01:28 +09:00
|
|
|
|
|
|
|
// Else, we just sum the payments
|
|
|
|
return payments
|
2019-03-05 13:54:34 +09:00
|
|
|
.Select(pay =>
|
|
|
|
{
|
|
|
|
var paymentMethodContribution = new Contribution();
|
2020-08-09 14:43:13 +02:00
|
|
|
paymentMethodContribution.PaymentMethodId = pay.GetPaymentMethodId();
|
2019-03-05 13:54:34 +09:00
|
|
|
paymentMethodContribution.Value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee;
|
2020-08-09 14:43:13 +02:00
|
|
|
var rate = p.GetPaymentMethod(paymentMethodContribution.PaymentMethodId).Rate;
|
2020-06-28 17:55:27 +09:00
|
|
|
paymentMethodContribution.CurrencyValue = rate * paymentMethodContribution.Value;
|
2019-03-05 13:54:34 +09:00
|
|
|
return paymentMethodContribution;
|
|
|
|
})
|
2019-02-19 16:15:14 +09:00
|
|
|
.ToArray();
|
2019-02-19 16:01:28 +09:00
|
|
|
})
|
2020-08-09 14:43:13 +02:00
|
|
|
.GroupBy(p => p.PaymentMethodId)
|
2019-03-05 13:54:34 +09:00
|
|
|
.ToDictionary(p => p.Key, p => new Contribution()
|
|
|
|
{
|
2020-08-09 14:43:13 +02:00
|
|
|
PaymentMethodId = p.Key,
|
2019-03-05 13:54:34 +09:00
|
|
|
Value = p.Select(v => v.Value).Sum(),
|
2019-03-05 13:58:13 +09:00
|
|
|
CurrencyValue = p.Select(v => v.CurrencyValue).Sum()
|
2019-03-05 13:54:34 +09:00
|
|
|
});
|
|
|
|
return new Contributions(contributions);
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2020-05-19 20:54:24 +02:00
|
|
|
public string[] GetDetailStringList(string field)
|
|
|
|
{
|
2020-06-28 17:55:27 +09:00
|
|
|
if (!Value.Children.ContainsKey(field) || !(Value.Children[field] is YamlSequenceNode sequenceNode))
|
2020-05-19 20:54:24 +02:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2020-06-28 17:55:27 +09:00
|
|
|
return sequenceNode.Children.Select(node => (node as YamlScalarNode)?.Value).Where(s => s != null).ToArray();
|
2020-05-19 20:54:24 +02:00
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-09-02 15:37:52 +02:00
|
|
|
|
|
|
|
public async Task UpdateOrCreateApp(AppData app)
|
|
|
|
{
|
|
|
|
using (var ctx = _ContextFactory.CreateContext())
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(app.Id))
|
|
|
|
{
|
|
|
|
app.Id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(20));
|
|
|
|
app.Created = DateTimeOffset.Now;
|
|
|
|
await ctx.Apps.AddAsync(app);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctx.Apps.Update(app);
|
|
|
|
ctx.Entry(app).Property(data => data.Created).IsModified = false;
|
|
|
|
ctx.Entry(app).Property(data => data.Id).IsModified = false;
|
|
|
|
ctx.Entry(app).Property(data => data.AppType).IsModified = false;
|
|
|
|
}
|
|
|
|
await ctx.SaveChangesAsync();
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 17:55:27 +09:00
|
|
|
|
2019-09-02 15:37:52 +02:00
|
|
|
private static bool TryParseJson(string json, out JObject result)
|
|
|
|
{
|
|
|
|
result = null;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
result = JObject.Parse(json);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool TryParsePosCartItems(string posData, out Dictionary<string, int> cartItems)
|
|
|
|
{
|
|
|
|
cartItems = null;
|
|
|
|
if (!TryParseJson(posData, out var posDataObj) ||
|
2020-06-28 17:55:27 +09:00
|
|
|
!posDataObj.TryGetValue("cart", out var cartObject))
|
|
|
|
return false;
|
2019-09-02 15:37:52 +02:00
|
|
|
cartItems = cartObject.Select(token => (JObject)token)
|
|
|
|
.ToDictionary(o => o.GetValue("id", StringComparison.InvariantCulture).ToString(),
|
2020-06-28 17:55:27 +09:00
|
|
|
o => int.Parse(o.GetValue("count", StringComparison.InvariantCulture).ToString(), CultureInfo.InvariantCulture));
|
2019-09-02 15:37:52 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-02-19 13:04:58 +09:00
|
|
|
}
|
|
|
|
}
|