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})");
            }
        }
예제 #2
0
        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 }));
        }
예제 #5
0
        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()
            });
        }
예제 #6
0
 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()));
        }
예제 #9
0
        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()));
        }
예제 #10
0
        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));
        }
예제 #11
0
    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));
    }
예제 #12
0
        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()));
        }
예제 #13
0
    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));
    }
예제 #14
0
        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)));
        }
예제 #15
0
        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 }));
        }
예제 #17
0
        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
                }
            });
        }
예제 #18
0
 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 }));
        }
예제 #21
0
        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()
            });