public static string FormatCurrency(decimal price, string currency, CurrencyNameTable currencies) { var provider = currencies.GetNumberFormatInfo(currency, true); var currencyData = currencies.GetCurrencyData(currency, true); var divisibility = currencyData.Divisibility; while (true) { var rounded = decimal.Round(price, divisibility, MidpointRounding.AwayFromZero); if ((Math.Abs(rounded - price) / price) < 0.001m) { price = rounded; break; } divisibility++; } if (divisibility != provider.CurrencyDecimalDigits) { provider = (NumberFormatInfo)provider.Clone(); provider.CurrencyDecimalDigits = divisibility; } if (currencyData.Crypto) { return(price.ToString("C", provider)); } else { return(price.ToString("C", provider) + $" ({currency})"); } }
public static bool TryParse(string str, out CurrencyValue value) { value = null; var match = _Regex.Match(str); if (!match.Success || !decimal.TryParse(match.Groups[1].Value, out var v)) { return(false); } var currency = match.Groups.Last().Value.ToUpperInvariant(); var currencyData = _CurrencyTable.GetCurrencyData(currency, false); if (currencyData == null) { return(false); } v = Math.Round(v, currencyData.Divisibility); value = new CurrencyValue() { Value = v, Currency = currency }; return(true); }
private IActionResult Validate(PaymentRequestBaseData data) { if (data is null) { return(BadRequest()); } if (data.Amount <= 0) { ModelState.AddModelError(nameof(data.Amount), "Please provide an amount greater than 0"); } if (string.IsNullOrEmpty(data.Currency) || _currencyNameTable.GetCurrencyData(data.Currency, false) == null) { ModelState.AddModelError(nameof(data.Currency), "Invalid currency"); } if (string.IsNullOrEmpty(data.Title)) { ModelState.AddModelError(nameof(data.Title), "Title is required"); } if (!string.IsNullOrEmpty(data.CustomCSSLink) && data.CustomCSSLink.Length > 500) { ModelState.AddModelError(nameof(data.CustomCSSLink), "CustomCSSLink is 500 chars max"); } return(!ModelState.IsValid ? this.CreateValidationError(ModelState) : null); }
public async Task <IActionResult> EditPaymentRequest(string id, UpdatePaymentRequestViewModel viewModel) { if (string.IsNullOrEmpty(viewModel.Currency) || _Currencies.GetCurrencyData(viewModel.Currency, false) == null) { ModelState.AddModelError(nameof(viewModel.Currency), "Invalid currency"); } var data = await _PaymentRequestRepository.FindPaymentRequest(id, GetUserId()); if (data == null && !string.IsNullOrEmpty(id)) { return(NotFound()); } if (!ModelState.IsValid) { viewModel.Stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), data?.StoreDataId); return(View(viewModel)); } if (data == null) { data = new PaymentRequestData(); } data.StoreDataId = viewModel.StoreId; var blob = data.GetBlob(); blob.Title = viewModel.Title; blob.Email = viewModel.Email; blob.Description = viewModel.Description; blob.Amount = viewModel.Amount; blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime(); blob.Currency = viewModel.Currency; blob.EmbeddedCSS = viewModel.EmbeddedCSS; blob.CustomCSSLink = viewModel.CustomCSSLink; blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts; data.SetBlob(blob); if (string.IsNullOrEmpty(id)) { data.Created = DateTimeOffset.UtcNow; } data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data); _EventAggregator.Publish(new PaymentRequestUpdated() { Data = data, PaymentRequestId = data.Id, }); TempData[WellKnownTempData.SuccessMessage] = "Saved"; return(RedirectToAction("EditPaymentRequest", new { id = data.Id })); }
public async Task <ViewPaymentRequestViewModel> GetPaymentRequest(string id, string userId = null) { var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null); if (pr == null) { return(null); } var blob = pr.GetBlob(); var rateRules = pr.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider); var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(id); var paymentStats = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true); var amountDue = blob.Amount - paymentStats.TotalCurrency; var pendingInvoice = invoices.SingleOrDefault(entity => entity.Status == InvoiceStatus.New); return(new ViewPaymentRequestViewModel(pr) { Archived = pr.Archived, AmountFormatted = _currencies.FormatCurrency(blob.Amount, blob.Currency), AmountCollected = paymentStats.TotalCurrency, AmountCollectedFormatted = _currencies.FormatCurrency(paymentStats.TotalCurrency, blob.Currency), AmountDue = amountDue, AmountDueFormatted = _currencies.FormatCurrency(amountDue, blob.Currency), CurrencyData = _currencies.GetCurrencyData(blob.Currency, true), LastUpdated = DateTime.Now, AnyPendingInvoice = pendingInvoice != null, PendingInvoiceHasPayments = pendingInvoice != null && pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None, Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice() { Id = entity.Id, Amount = entity.ProductInformation.Price, AmountFormatted = _currencies.FormatCurrency(entity.ProductInformation.Price, blob.Currency), Currency = entity.ProductInformation.Currency, ExpiryDate = entity.ExpirationTime.DateTime, Status = entity.GetInvoiceState().ToString(), Payments = entity.GetPayments().Select(paymentEntity => { var paymentData = paymentEntity.GetCryptoPaymentData(); var paymentMethodId = paymentEntity.GetPaymentMethodId(); string txId = paymentData.GetPaymentId(); string link = GetTransactionLink(paymentMethodId, txId); return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment() { Amount = paymentData.GetValue(), PaymentMethod = paymentMethodId.ToString(), Link = link, Id = txId }; }).ToList() }).ToList() }); }
public async Task <IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request) { if (request is null) { ModelState.AddModelError(string.Empty, "Missing body"); return(this.CreateValidationError(ModelState)); } if (request.Amount <= 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0."); } if (request.Name is String name && name.Length > 50) { ModelState.AddModelError(nameof(request.Name), "The name should be maximum 50 characters."); } if (request.Currency is String currency) { request.Currency = currency.ToUpperInvariant().Trim(); if (_currencyNameTable.GetCurrencyData(request.Currency, false) is null) { ModelState.AddModelError(nameof(request.Currency), "Invalid currency"); } } else { ModelState.AddModelError(nameof(request.Currency), "This field is required"); } if (request.ExpiresAt is DateTimeOffset expires && request.StartsAt is DateTimeOffset start && expires < start) { ModelState.AddModelError(nameof(request.ExpiresAt), $"expiresAt should be higher than startAt"); } if (request.Period <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.Period), $"The period should be positive"); } if (request.BOLT11Expiration <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.BOLT11Expiration), $"The BOLT11 expiration should be positive"); } PaymentMethodId?[]? paymentMethods = null; if (request.PaymentMethods is { } paymentMethodsStr) { paymentMethods = paymentMethodsStr.Select(s => { PaymentMethodId.TryParse(s, out var pmi); return(pmi); }).ToArray(); var supported = (await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData())).ToArray(); for (int i = 0; i < paymentMethods.Length; i++) { if (!supported.Contains(paymentMethods[i])) { request.AddModelError(paymentRequest => paymentRequest.PaymentMethods[i], "Invalid or unsupported payment method", this); } } }
public async Task <IActionResult> ViewPullPayment(string pullPaymentId) { using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { return(NotFound()); } var blob = pp.GetBlob(); var payouts = (await ctx.Payouts.GetPayoutInPeriod(pp) .OrderByDescending(o => o.Date) .ToListAsync()) .Select(o => new { Entity = o, Blob = o.GetBlob(_serializerSettings), ProofBlob = _payoutHandlers.FindPayoutHandler(o.GetPaymentMethodId())?.ParseProof(o) }); var cd = _currencyNameTable.GetCurrencyData(blob.Currency, false); var totalPaid = payouts.Where(p => p.Entity.State != PayoutState.Cancelled).Select(p => p.Blob.Amount).Sum(); var amountDue = blob.Limit - totalPaid; ViewPullPaymentModel vm = new (pp, DateTimeOffset.UtcNow) { AmountFormatted = _currencyNameTable.FormatCurrency(blob.Limit, blob.Currency), AmountCollected = totalPaid, AmountCollectedFormatted = _currencyNameTable.FormatCurrency(totalPaid, blob.Currency), AmountDue = amountDue, ClaimedAmount = amountDue, AmountDueFormatted = _currencyNameTable.FormatCurrency(amountDue, blob.Currency), CurrencyData = cd, StartDate = pp.StartDate, LastRefreshed = DateTime.UtcNow, Payouts = payouts .Select(entity => new ViewPullPaymentModel.PayoutLine { Id = entity.Entity.Id, Amount = entity.Blob.Amount, AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency), Currency = blob.Currency, Status = entity.Entity.State, Destination = entity.Blob.Destination, PaymentMethod = PaymentMethodId.Parse(entity.Entity.PaymentMethodId), Link = entity.ProofBlob?.Link, TransactionId = entity.ProofBlob?.Id }).ToList() }; vm.IsPending &= vm.AmountDue > 0.0m; return(View(nameof(ViewPullPayment), vm)); }
public async Task <IActionResult> GetPayouts(string pullPaymentId, bool includeCancelled = false) { if (pullPaymentId is null) { return(NotFound()); } var pp = await _pullPaymentService.GetPullPayment(pullPaymentId); if (pp is null) { return(NotFound()); } using var ctx = _dbContextFactory.CreateContext(); var payouts = await ctx.Payouts.Where(p => p.PullPaymentDataId == pullPaymentId) .Where(p => p.State != Data.PayoutState.Cancelled || includeCancelled) .ToListAsync(); var cd = _currencyNameTable.GetCurrencyData(pp.GetBlob().Currency, false); return(base.Ok(payouts .Select(p => ToModel(p, cd)).ToList())); }
public async Task <DataWrapper <NBitpayClient.Rate[]> > GetRates() { var allRates = (await _RateProvider.GetRatesAsync()); return(new DataWrapper <NBitpayClient.Rate[]> (allRates.Select(r => new NBitpayClient.Rate() { Code = r.Currency, Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name, Value = r.Value }).Where(n => n.Name != null).ToArray())); }
public async Task <IActionResult> ViewPullPayment(string pullPaymentId) { using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { return(NotFound()); } var blob = pp.GetBlob(); var payouts = (await ctx.Payouts.GetPayoutInPeriod(pp) .OrderByDescending(o => o.Date) .ToListAsync()) .Select(o => new { Entity = o, Blob = o.GetBlob(_serializerSettings), TransactionId = o.GetProofBlob(_serializerSettings)?.TransactionId?.ToString() }); var cd = _currencyNameTable.GetCurrencyData(blob.Currency, false); var totalPaid = payouts.Where(p => p.Entity.State != PayoutState.Cancelled).Select(p => p.Blob.Amount).Sum(); var amountDue = blob.Limit - totalPaid; ViewPullPaymentModel vm = new ViewPullPaymentModel(pp, DateTimeOffset.UtcNow) { AmountFormatted = _currencyNameTable.FormatCurrency(blob.Limit, blob.Currency), AmountCollected = totalPaid, AmountCollectedFormatted = _currencyNameTable.FormatCurrency(totalPaid, blob.Currency), AmountDue = amountDue, ClaimedAmount = amountDue, AmountDueFormatted = _currencyNameTable.FormatCurrency(amountDue, blob.Currency), CurrencyData = cd, LastUpdated = DateTime.Now, Payouts = payouts .Select(entity => new ViewPullPaymentModel.PayoutLine() { Id = entity.Entity.Id, Amount = entity.Blob.Amount, AmountFormatted = _currencyNameTable.FormatCurrency(entity.Blob.Amount, blob.Currency), Currency = blob.Currency, Status = entity.Entity.State.ToString(), Destination = entity.Blob.Destination.Address.ToString(), Link = GetTransactionLink(_networkProvider.GetNetwork <BTCPayNetwork>(entity.Entity.GetPaymentMethodId().CryptoCode), entity.TransactionId), TransactionId = entity.TransactionId }).ToList() }; vm.IsPending &= vm.AmountDue > 0.0m; return(View(nameof(ViewPullPayment), vm)); }
public async Task <IViewComponentResult> InvokeAsync(StoreLightningBalanceViewModel vm) { if (vm.Store == null) { throw new ArgumentNullException(nameof(vm.Store)); } if (vm.CryptoCode == null) { throw new ArgumentNullException(nameof(vm.CryptoCode)); } vm.DefaultCurrency = vm.Store.GetStoreBlob().DefaultCurrency; vm.CurrencyData = _currencies.GetCurrencyData(vm.DefaultCurrency, true); if (vm.InitialRendering) { return(View(vm)); } try { var lightningClient = GetLightningClient(vm.Store, vm.CryptoCode); var balance = await lightningClient.GetBalance(); vm.Balance = balance; vm.TotalOnchain = balance.OnchainBalance != null ? (balance.OnchainBalance.Confirmed ?? 0L) + (balance.OnchainBalance.Reserved ?? 0L) + (balance.OnchainBalance.Unconfirmed ?? 0L) : null; vm.TotalOffchain = balance.OffchainBalance != null ? (balance.OffchainBalance.Opening ?? 0) + (balance.OffchainBalance.Local ?? 0) + (balance.OffchainBalance.Closing ?? 0) : null; } catch (NotSupportedException) { // not all implementations support balance fetching vm.ProblemDescription = "Your node does not support balance fetching."; } catch { // general error vm.ProblemDescription = "Could not fetch Lightning balance."; } return(View(vm)); }
public async Task <IActionResult> GetRates2(string cryptoCode = null, string storeId = null) { cryptoCode = cryptoCode ?? "BTC"; var network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { return(NotFound()); } RateRules rules = null; if (storeId != null) { var store = await _StoreRepo.FindStore(storeId); if (store == null) { return(NotFound()); } rules = store.GetStoreBlob().GetRateRules(); } var rateProvider = _RateProviderFactory.GetRateProvider(network, rules); if (rateProvider == null) { return(NotFound()); } var allRates = (await rateProvider.GetRatesAsync()); return(Json(allRates.Select(r => new NBitpayClient.Rate() { Code = r.Currency, Name = _CurrencyNameTable.GetCurrencyData(r.Currency)?.Name, Value = r.Value }).Where(n => n.Name != null).ToArray())); }
public async Task <IViewComponentResult> InvokeAsync(StoreData store) { var cryptoCode = _networkProvider.DefaultNetwork.CryptoCode; var walletId = new WalletId(store.Id, cryptoCode); var data = await _walletHistogramService.GetHistogram(store, walletId, DefaultType); var defaultCurrency = store.GetStoreBlob().DefaultCurrency; var vm = new StoreWalletBalanceViewModel { Store = store, CryptoCode = cryptoCode, CurrencyData = _currencies.GetCurrencyData(defaultCurrency, true), DefaultCurrency = defaultCurrency, WalletId = walletId, Type = DefaultType }; if (data != null) { vm.Balance = data.Balance; vm.Series = data.Series; vm.Labels = data.Labels; } else { using CancellationTokenSource cts = new (TimeSpan.FromSeconds(3)); var wallet = _walletProvider.GetWallet(_networkProvider.DefaultNetwork); var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode); var balance = await wallet.GetBalance(derivation.AccountDerivation, cts.Token); vm.Balance = balance.Available.GetValue(); } return(View(vm)); }
public async Task <IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request) { if (request is null) { ModelState.AddModelError(string.Empty, "Missing body"); return(this.CreateValidationError(ModelState)); } if (request.Amount <= 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0."); } if (request.Name is String name && name.Length > 50) { ModelState.AddModelError(nameof(request.Name), "The name should be maximum 50 characters."); } if (request.Currency is String currency) { request.Currency = currency.ToUpperInvariant().Trim(); if (_currencyNameTable.GetCurrencyData(request.Currency, false) is null) { ModelState.AddModelError(nameof(request.Currency), "Invalid currency"); } } else { ModelState.AddModelError(nameof(request.Currency), "This field is required"); } if (request.ExpiresAt is DateTimeOffset expires && request.StartsAt is DateTimeOffset start && expires < start) { ModelState.AddModelError(nameof(request.ExpiresAt), $"expiresAt should be higher than startAt"); } if (request.Period <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.Period), $"The period should be positive"); } PaymentMethodId[] paymentMethods = null; if (request.PaymentMethods is string[] paymentMethodsStr) { paymentMethods = paymentMethodsStr.Select(p => new PaymentMethodId(p, PaymentTypes.BTCLike)).ToArray(); foreach (var p in paymentMethods) { var n = _networkProvider.GetNetwork <BTCPayNetwork>(p.CryptoCode); if (n is null) { ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method"); } if (n.ReadonlyWallet) { ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method (We do not support the crypto currency for refund)"); } } if (paymentMethods.Any(p => _networkProvider.GetNetwork <BTCPayNetwork>(p.CryptoCode) is null)) { ModelState.AddModelError(nameof(request.PaymentMethods), "Invalid payment method"); } } else { ModelState.AddModelError(nameof(request.PaymentMethods), "This field is required"); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } var ppId = await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment() { StartsAt = request.StartsAt, ExpiresAt = request.ExpiresAt, Period = request.Period, Name = request.Name, Amount = request.Amount, Currency = request.Currency, StoreId = storeId, PaymentMethodIds = paymentMethods }); var pp = await _pullPaymentService.GetPullPayment(ppId, false); return(this.Ok(CreatePullPaymentData(pp))); }
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 || entity.Status == InvoiceStatus.Confirmed).ToArray(); var pendingInvoices = invoices.Where(entity => !(entity.Status == InvoiceStatus.Complete || entity.Status == InvoiceStatus.Confirmed)).ToArray(); var pendingPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount); var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount); 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, Enabled = settings.Enabled, DisqusEnabled = settings.DisqusEnabled, SoundsEnabled = settings.SoundsEnabled, DisqusShortname = settings.DisqusShortname, AnimationsEnabled = settings.AnimationsEnabled, ResetEveryAmount = settings.ResetEveryAmount, DisplayPerksRanking = settings.DisplayPerksRanking, PerkCount = perkCount, NeverReset = settings.ResetEvery == CrowdfundResetEvery.Never, Sounds = settings.Sounds, AnimationColors = settings.AnimationColors, CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true), Info = new ViewCrowdfundViewModel.CrowdfundInfo() { TotalContributors = invoices.Length, ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100, PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100, LastUpdated = DateTime.Now, PaymentStats = currentPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), PendingPaymentStats = pendingPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), LastResetDate = lastResetDate, NextResetDate = nextResetDate, CurrentPendingAmount = pendingPayments.TotalCurrency, CurrentAmount = currentPayments.TotalCurrency } }); }
public async Task <IActionResult> EditPaymentRequest(string payReqId, UpdatePaymentRequestViewModel viewModel) { if (!string.IsNullOrEmpty(viewModel.Currency) && _Currencies.GetCurrencyData(viewModel.Currency, false) == null) { ModelState.AddModelError(nameof(viewModel.Currency), "Invalid currency"); } if (string.IsNullOrEmpty(viewModel.Currency)) { viewModel.Currency = null; } var store = GetCurrentStore(); var paymentRequest = GetCurrentPaymentRequest(); if (paymentRequest == null && !string.IsNullOrEmpty(payReqId)) { return(NotFound()); } if (paymentRequest?.Archived is true && viewModel.Archived) { ModelState.AddModelError(string.Empty, "You cannot edit an archived payment request."); } if (!ModelState.IsValid) { return(View(nameof(EditPaymentRequest), viewModel)); } var data = paymentRequest ?? new PaymentRequestData(); data.StoreDataId = viewModel.StoreId; data.Archived = viewModel.Archived; var blob = data.GetBlob(); blob.Title = viewModel.Title; blob.Email = viewModel.Email; blob.Description = viewModel.Description; blob.Amount = viewModel.Amount; blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime(); blob.Currency = viewModel.Currency ?? store.GetStoreBlob().DefaultCurrency; blob.EmbeddedCSS = viewModel.EmbeddedCSS; blob.CustomCSSLink = viewModel.CustomCSSLink; blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts; data.SetBlob(blob); if (string.IsNullOrEmpty(payReqId)) { data.Created = DateTimeOffset.UtcNow; } data = await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(data); _EventAggregator.Publish(new PaymentRequestUpdated { Data = data, PaymentRequestId = data.Id, }); TempData[WellKnownTempData.SuccessMessage] = "Saved"; return(RedirectToAction(nameof(EditPaymentRequest), new { storeId = store.Id, payReqId = data.Id })); }
private async Task <ViewCrowdfundViewModel> GetInfo(AppData appData) { 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.UtcNow >= 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(IsComplete).ToArray(); var pendingInvoices = invoices.Where(IsPending).ToArray(); var paidInvoices = invoices.Where(IsPaid).ToArray(); var pendingPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, pendingInvoices, !settings.EnforceTargetAmount); var currentPayments = GetContributionsByPaymentMethodId(settings.TargetCurrency, completeInvoices, !settings.EnforceTargetAmount); var perkCount = paidInvoices .Where(entity => !string.IsNullOrEmpty(entity.Metadata.ItemCode)) .GroupBy(entity => entity.Metadata.ItemCode) .ToDictionary(entities => entities.Key, entities => entities.Count()); Dictionary <string, decimal> perkValue = new(); if (settings.DisplayPerksValue) { perkValue = paidInvoices .Where(entity => entity.Currency.Equals(settings.TargetCurrency, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(entity.Metadata.ItemCode)) .GroupBy(entity => entity.Metadata.ItemCode) .ToDictionary(entities => entities.Key, entities => entities.Sum(entity => entity.GetPayments(true).Sum(pay => { var paymentMethodId = pay.GetPaymentMethodId(); var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee; var rate = entity.GetPaymentMethod(paymentMethodId).Rate; return(rate * value); }))); } 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, Enabled = settings.Enabled, DisqusEnabled = settings.DisqusEnabled, SoundsEnabled = settings.SoundsEnabled, DisqusShortname = settings.DisqusShortname, AnimationsEnabled = settings.AnimationsEnabled, ResetEveryAmount = settings.ResetEveryAmount, ResetEvery = Enum.GetName(typeof(CrowdfundResetEvery), settings.ResetEvery), DisplayPerksRanking = settings.DisplayPerksRanking, PerkCount = perkCount, PerkValue = perkValue, NeverReset = settings.ResetEvery == CrowdfundResetEvery.Never, Sounds = settings.Sounds, AnimationColors = settings.AnimationColors, CurrencyData = _Currencies.GetCurrencyData(settings.TargetCurrency, true), CurrencyDataPayments = Enumerable.DistinctBy(currentPayments.Select(pair => pair.Key) .Concat(pendingPayments.Select(pair => pair.Key)) .Select(id => _Currencies.GetCurrencyData(id.CryptoCode, true)), data => data.Code) .ToDictionary(data => data.Code, data => data), Info = new CrowdfundInfo { TotalContributors = paidInvoices.Length, ProgressPercentage = (currentPayments.TotalCurrency / settings.TargetAmount) * 100, PendingProgressPercentage = (pendingPayments.TotalCurrency / settings.TargetAmount) * 100, LastUpdated = DateTime.UtcNow, PaymentStats = currentPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), PendingPaymentStats = pendingPayments.ToDictionary(c => c.Key.ToString(), c => c.Value.Value), LastResetDate = lastResetDate, NextResetDate = nextResetDate, CurrentPendingAmount = pendingPayments.TotalCurrency, CurrentAmount = currentPayments.TotalCurrency } }); }
public CurrencyData GetCurrencyData(string currency, bool useFallback) { return(_Currencies.GetCurrencyData(currency, useFallback)); }
public async Task <ViewPaymentRequestViewModel> GetPaymentRequest(string id, string userId = null) { var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null); if (pr == null) { return(null); } var blob = pr.GetBlob(); var rateRules = pr.StoreData.GetStoreBlob().GetRateRules(_BtcPayNetworkProvider); var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(id); var paymentStats = _AppService.GetCurrentContributionAmountStats(invoices, true); var amountCollected = await _AppService.GetCurrentContributionAmount(paymentStats, blob.Currency, rateRules); var amountDue = blob.Amount - amountCollected; return(new ViewPaymentRequestViewModel(pr) { AmountFormatted = _currencies.FormatCurrency(blob.Amount, blob.Currency), AmountCollected = amountCollected, AmountCollectedFormatted = _currencies.FormatCurrency(amountCollected, blob.Currency), AmountDue = amountDue, AmountDueFormatted = _currencies.FormatCurrency(amountDue, blob.Currency), CurrencyData = _currencies.GetCurrencyData(blob.Currency, true), LastUpdated = DateTime.Now, AnyPendingInvoice = invoices.Any(entity => entity.Status == InvoiceStatus.New), Invoices = invoices.Select(entity => new ViewPaymentRequestViewModel.PaymentRequestInvoice() { Id = entity.Id, Amount = entity.ProductInformation.Price, Currency = entity.ProductInformation.Currency, ExpiryDate = entity.ExpirationTime.DateTime, Status = entity.GetInvoiceState().ToString(), Payments = entity.GetPayments().Select(paymentEntity => { var paymentNetwork = _BtcPayNetworkProvider.GetNetwork(paymentEntity.GetCryptoCode()); var paymentData = paymentEntity.GetCryptoPaymentData(); string link = null; string txId = null; switch (paymentData) { case Payments.Bitcoin.BitcoinLikePaymentData onChainPaymentData: txId = onChainPaymentData.Outpoint.Hash.ToString(); link = string.Format(CultureInfo.InvariantCulture, paymentNetwork.BlockExplorerLink, txId); break; case LightningLikePaymentData lightningLikePaymentData: txId = lightningLikePaymentData.BOLT11; break; } return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment() { Amount = paymentData.GetValue(), PaymentMethod = paymentEntity.GetPaymentMethodId().ToString(), Link = link, Id = txId }; }).ToList() }).ToList()
public async Task <IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model) { if (CurrentStore is null) { return(NotFound()); } var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore); model.PaymentMethodItems = paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true)); model.Name ??= string.Empty; model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty; model.PaymentMethods ??= new List <string>(); if (!model.PaymentMethods.Any()) { // Since we assign all payment methods to be selected by default above we need to update // them here to reflect user's selection so that they can correct their mistake model.PaymentMethodItems = paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), false)); ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method"); } if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null) { ModelState.AddModelError(nameof(model.Currency), "Invalid currency"); } if (model.Amount <= 0.0m) { ModelState.AddModelError(nameof(model.Amount), "The amount should be more than zero"); } if (model.Name.Length > 50) { ModelState.AddModelError(nameof(model.Name), "The name should be maximum 50 characters."); } var selectedPaymentMethodIds = model.PaymentMethods.Select(PaymentMethodId.Parse).ToArray(); if (!selectedPaymentMethodIds.All(id => selectedPaymentMethodIds.Contains(id))) { ModelState.AddModelError(nameof(model.Name), "Not all payment methods are supported"); } if (!ModelState.IsValid) { return(View(model)); } await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment() { Name = model.Name, Amount = model.Amount, Currency = model.Currency, StoreId = storeId, PaymentMethodIds = selectedPaymentMethodIds, EmbeddedCSS = model.EmbeddedCSS, CustomCSSLink = model.CustomCSSLink, BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration) }); this.TempData.SetStatusMessageModel(new StatusMessageModel() { Message = "Pull payment request created", Severity = StatusMessageModel.StatusSeverity.Success }); return(RedirectToAction(nameof(PullPayments), new { storeId = storeId })); }
public async Task <ViewPaymentRequestViewModel> GetPaymentRequest(string id, string userId = null) { var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null); if (pr == null) { return(null); } var blob = pr.GetBlob(); var invoices = await _PaymentRequestRepository.GetInvoicesForPaymentRequest(id); var paymentStats = _AppService.GetContributionsByPaymentMethodId(blob.Currency, invoices, true); var amountDue = blob.Amount - paymentStats.TotalCurrency; var pendingInvoice = invoices.OrderByDescending(entity => entity.InvoiceTime) .FirstOrDefault(entity => entity.Status == InvoiceStatusLegacy.New); return(new ViewPaymentRequestViewModel(pr) { Archived = pr.Archived, AmountFormatted = _currencies.FormatCurrency(blob.Amount, blob.Currency), AmountCollected = paymentStats.TotalCurrency, AmountCollectedFormatted = _currencies.FormatCurrency(paymentStats.TotalCurrency, blob.Currency), AmountDue = amountDue, AmountDueFormatted = _currencies.FormatCurrency(amountDue, blob.Currency), CurrencyData = _currencies.GetCurrencyData(blob.Currency, true), LastUpdated = DateTime.Now, AnyPendingInvoice = pendingInvoice != null, PendingInvoiceHasPayments = pendingInvoice != null && pendingInvoice.ExceptionStatus != InvoiceExceptionStatus.None, Invoices = invoices.Select(entity => { var state = entity.GetInvoiceState(); return new ViewPaymentRequestViewModel.PaymentRequestInvoice { Id = entity.Id, Amount = entity.Price, AmountFormatted = _currencies.FormatCurrency(entity.Price, blob.Currency), Currency = entity.Currency, ExpiryDate = entity.ExpirationTime.DateTime, State = state, StateFormatted = state.ToString(), Payments = entity .GetPayments(true) .Select(paymentEntity => { var paymentData = paymentEntity.GetCryptoPaymentData(); var paymentMethodId = paymentEntity.GetPaymentMethodId(); if (paymentData is null || paymentMethodId is null) { return null; } string txId = paymentData.GetPaymentId(); string link = GetTransactionLink(paymentMethodId, txId); var paymentMethod = entity.GetPaymentMethod(paymentMethodId); var amount = paymentData.GetValue(); var rate = paymentMethod.Rate; var paid = (amount - paymentEntity.NetworkFee) * rate; return new ViewPaymentRequestViewModel.PaymentRequestInvoicePayment { Amount = amount, Paid = paid, ReceivedDate = paymentEntity.ReceivedTime.DateTime, PaidFormatted = _currencies.FormatCurrency(paid, blob.Currency), RateFormatted = _currencies.FormatCurrency(rate, blob.Currency), PaymentMethod = paymentMethodId.ToPrettyString(), Link = link, Id = txId }; }) .Where(payment => payment != null) .ToList() }; }).ToList() });