public async Task <IViewComponentResult> InvokeAsync(StoreData store) { var userId = _userManager.GetUserId(UserClaimsPrincipal); var invoiceEntities = await _invoiceRepo.GetInvoices(new InvoiceQuery { UserId = userId, StoreId = new [] { store.Id }, Take = 5 }); var invoices = new List <StoreRecentInvoiceViewModel>(); foreach (var invoice in invoiceEntities) { var state = invoice.GetInvoiceState(); invoices.Add(new StoreRecentInvoiceViewModel { Date = invoice.InvoiceTime, Status = state, InvoiceId = invoice.Id, OrderId = invoice.Metadata.OrderId ?? string.Empty, AmountCurrency = _currencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency), }); } var vm = new StoreRecentInvoicesViewModel { Store = store, Invoices = invoices }; return(View(vm)); }
public async Task <IActionResult> ClaimPullPayment(string pullPaymentId, ViewPullPaymentModel vm) { using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists"); } var ppBlob = pp.GetBlob(); var network = _networkProvider.GetNetwork <BTCPayNetwork>(ppBlob.SupportedPaymentMethods.Single().CryptoCode); var paymentMethodId = ppBlob.SupportedPaymentMethods.Single(); var payoutHandler = _payoutHandlers.FirstOrDefault(handler => handler.CanHandle(paymentMethodId)); IClaimDestination destination = await payoutHandler?.ParseClaimDestination(paymentMethodId, vm.Destination); if (destination is null) { ModelState.AddModelError(nameof(vm.Destination), $"Invalid destination"); } if (!ModelState.IsValid) { return(await ViewPullPayment(pullPaymentId)); } var result = await _pullPaymentHostedService.Claim(new ClaimRequest() { Destination = destination, PullPaymentId = pullPaymentId, Value = vm.ClaimedAmount, PaymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike) }); if (result.Result != ClaimRequest.ClaimResult.Ok) { if (result.Result == ClaimRequest.ClaimResult.AmountTooLow) { ModelState.AddModelError(nameof(vm.ClaimedAmount), ClaimRequest.GetErrorMessage(result.Result)); } else { ModelState.AddModelError(string.Empty, ClaimRequest.GetErrorMessage(result.Result)); } return(await ViewPullPayment(pullPaymentId)); } else { TempData.SetStatusMessageModel(new StatusMessageModel() { Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting approval.", Severity = StatusMessageModel.StatusSeverity.Success }); } return(RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId = pullPaymentId })); }
public async Task <IViewComponentResult> InvokeAsync(StoreRecentInvoicesViewModel vm) { if (vm.Store == null) { throw new ArgumentNullException(nameof(vm.Store)); } if (vm.CryptoCode == null) { throw new ArgumentNullException(nameof(vm.CryptoCode)); } if (vm.InitialRendering) { return(View(vm)); } var userId = _userManager.GetUserId(UserClaimsPrincipal); var invoiceEntities = await _invoiceRepo.GetInvoices(new InvoiceQuery { UserId = userId, StoreId = new [] { vm.Store.Id }, IncludeArchived = false, IncludeRefunds = true, Take = 5 }); vm.Invoices = (from invoice in invoiceEntities let state = invoice.GetInvoiceState() select new StoreRecentInvoiceViewModel { Date = invoice.InvoiceTime, Status = state, HasRefund = invoice.Refunds.Any(), InvoiceId = invoice.Id, OrderId = invoice.Metadata.OrderId ?? string.Empty, AmountCurrency = _currencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency), }).ToList(); return(View(vm)); }
public async Task <IActionResult> ClaimPullPayment(string pullPaymentId, ViewPullPaymentModel vm) { using var ctx = _dbContextFactory.CreateContext(); var pp = await ctx.PullPayments.FindAsync(pullPaymentId); if (pp is null) { ModelState.AddModelError(nameof(pullPaymentId), "This pull payment does not exists"); } var ppBlob = pp.GetBlob(); var paymentMethodId = ppBlob.SupportedPaymentMethods.FirstOrDefault(id => vm.SelectedPaymentMethod == id.ToString()); var payoutHandler = paymentMethodId is null ? null : _payoutHandlers.FindPayoutHandler(paymentMethodId); if (payoutHandler is null) { ModelState.AddModelError(nameof(vm.SelectedPaymentMethod), "Invalid destination with selected payment method"); return(await ViewPullPayment(pullPaymentId)); } var destination = await payoutHandler.ParseAndValidateClaimDestination(paymentMethodId, vm.Destination, ppBlob); if (destination.destination is null) { ModelState.AddModelError(nameof(vm.Destination), destination.error ?? "Invalid destination with selected payment method"); return(await ViewPullPayment(pullPaymentId)); } if (vm.ClaimedAmount == 0) { ModelState.AddModelError(nameof(vm.ClaimedAmount), "Amount is required"); } else { var amount = ppBlob.Currency == "SATS" ? new Money(vm.ClaimedAmount, MoneyUnit.Satoshi).ToUnit(MoneyUnit.BTC) : vm.ClaimedAmount; if (destination.destination.Amount != null && amount != destination.destination.Amount) { var implied = _currencyNameTable.DisplayFormatCurrency(destination.destination.Amount.Value, paymentMethodId.CryptoCode); var provided = _currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency); ModelState.AddModelError(nameof(vm.ClaimedAmount), $"Amount implied in destination ({implied}) does not match the payout amount provided ({provided})."); } } if (!ModelState.IsValid) { return(await ViewPullPayment(pullPaymentId)); } var result = await _pullPaymentHostedService.Claim(new ClaimRequest() { Destination = destination.destination, PullPaymentId = pullPaymentId, Value = vm.ClaimedAmount, PaymentMethodId = paymentMethodId }); if (result.Result != ClaimRequest.ClaimResult.Ok) { if (result.Result == ClaimRequest.ClaimResult.AmountTooLow) { ModelState.AddModelError(nameof(vm.ClaimedAmount), ClaimRequest.GetErrorMessage(result.Result)); } else { ModelState.AddModelError(string.Empty, ClaimRequest.GetErrorMessage(result.Result)); } return(await ViewPullPayment(pullPaymentId)); } TempData.SetStatusMessageModel(new StatusMessageModel { Message = $"Your claim request of {_currencyNameTable.DisplayFormatCurrency(vm.ClaimedAmount, ppBlob.Currency)} to {vm.Destination} has been submitted and is awaiting {(result.PayoutData.State == PayoutState.AwaitingApproval? "approval": "payment")}.", Severity = StatusMessageModel.StatusSeverity.Success }); return(RedirectToAction(nameof(ViewPullPayment), new { pullPaymentId })); }
public async Task <IActionResult> PullPayments(string storeId, int skip = 0, int count = 50, string sortOrder = "desc") { await using var ctx = _dbContextFactory.CreateContext(); var now = DateTimeOffset.UtcNow; var ppsQuery = ctx.PullPayments .Include(data => data.Payouts) .Where(p => p.StoreId == storeId && !p.Archived); if (sortOrder != null) { switch (sortOrder) { case "desc": ViewData["NextStartSortOrder"] = "asc"; ppsQuery = ppsQuery.OrderByDescending(p => p.StartDate); break; case "asc": ppsQuery = ppsQuery.OrderBy(p => p.StartDate); ViewData["NextStartSortOrder"] = "desc"; break; } } var vm = this.ParseListQuery(new PullPaymentsModel() { Skip = skip, Count = count, Total = await ppsQuery.CountAsync() }); var pps = (await ppsQuery .Skip(vm.Skip) .Take(vm.Count) .ToListAsync() ); foreach (var pp in pps) { var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed || p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now)) .Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment || p.State == PayoutState.AwaitingApproval) && p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); ; var ppBlob = pp.GetBlob(); var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true); var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true); var period = pp.GetPeriod(now); vm.PullPayments.Add(new PullPaymentsModel.PullPaymentModel() { StartDate = pp.StartDate, EndDate = pp.EndDate, Id = pp.Id, Name = ppBlob.Name, Progress = new PullPaymentsModel.PullPaymentModel.ProgressModel() { CompletedPercent = (int)(totalCompleted / ppBlob.Limit * 100m), AwaitingPercent = (int)(totalAwaiting / ppBlob.Limit * 100m), Awaiting = totalAwaiting.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Completed = totalCompleted.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency), ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null, EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null } });
public async Task <IActionResult> PullPayments( string storeId, PullPaymentState pullPaymentState, int skip = 0, int count = 50, string sortOrder = "desc" ) { await using var ctx = _dbContextFactory.CreateContext(); var now = DateTimeOffset.UtcNow; var ppsQuery = ctx.PullPayments .Include(data => data.Payouts) .Where(p => p.StoreId == storeId); if (sortOrder != null) { switch (sortOrder) { case "desc": ViewData["NextStartSortOrder"] = "asc"; ppsQuery = ppsQuery.OrderByDescending(p => p.StartDate); break; case "asc": ppsQuery = ppsQuery.OrderBy(p => p.StartDate); ViewData["NextStartSortOrder"] = "desc"; break; } } var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData()); if (!paymentMethods.Any()) { TempData.SetStatusMessageModel(new StatusMessageModel { Message = "You must enable at least one payment method before creating a pull payment.", Severity = StatusMessageModel.StatusSeverity.Error }); return(RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId })); } var vm = this.ParseListQuery(new PullPaymentsModel { Skip = skip, Count = count, Total = await ppsQuery.CountAsync(), ActiveState = pullPaymentState }); switch (pullPaymentState) { case PullPaymentState.Active: ppsQuery = ppsQuery .Where( p => !p.Archived && (p.EndDate != null ? p.EndDate > DateTimeOffset.UtcNow : true) && p.StartDate <= DateTimeOffset.UtcNow ); break; case PullPaymentState.Archived: ppsQuery = ppsQuery.Where(p => p.Archived); break; case PullPaymentState.Expired: ppsQuery = ppsQuery.Where(p => DateTimeOffset.UtcNow > p.EndDate); break; case PullPaymentState.Future: ppsQuery = ppsQuery.Where(p => p.StartDate > DateTimeOffset.UtcNow); break; } var pps = (await ppsQuery .Skip(vm.Skip) .Take(vm.Count) .ToListAsync() ); foreach (var pp in pps) { var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed || p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now)) .Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment || p.State == PayoutState.AwaitingApproval) && p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum(); ; var ppBlob = pp.GetBlob(); var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true); var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true); var period = pp.GetPeriod(now); vm.PullPayments.Add(new PullPaymentsModel.PullPaymentModel() { StartDate = pp.StartDate, EndDate = pp.EndDate, Id = pp.Id, Name = ppBlob.Name, Progress = new PullPaymentsModel.PullPaymentModel.ProgressModel() { CompletedPercent = (int)(totalCompleted / ppBlob.Limit * 100m), AwaitingPercent = (int)(totalAwaiting / ppBlob.Limit * 100m), Awaiting = totalAwaiting.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Completed = totalCompleted.RoundToSignificant(ni.Divisibility).ToString("C", nfi), Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency), ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null, EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null, },
public async Task <IActionResult> ViewCustodianAccount(string storeId, string accountId) { var vm = new ViewCustodianAccountViewModel(); var custodianAccount = await _custodianAccountRepository.FindById(storeId, accountId); if (custodianAccount == null) { return(NotFound()); } var custodian = _custodianRegistry.GetCustodianByCode(custodianAccount.CustodianCode); if (custodian == null) { // TODO The custodian account is broken. The custodian is no longer available. Maybe delete the custodian account? return(NotFound()); } vm.Custodian = custodian; vm.CustodianAccount = custodianAccount; var store = GetCurrentStore(); var storeBlob = BTCPayServer.Data.StoreDataExtensions.GetStoreBlob(store); var defaultCurrency = storeBlob.DefaultCurrency; vm.DefaultCurrency = defaultCurrency; try { var assetBalances = new Dictionary <string, AssetBalanceInfo>(); var assetBalancesData = await custodian.GetAssetBalancesAsync(custodianAccount.GetBlob(), cancellationToken : default); foreach (var pair in assetBalancesData) { var asset = pair.Key; assetBalances.Add(asset, new AssetBalanceInfo { Asset = asset, Qty = pair.Value } ); } if (custodian is ICanTrade tradingCustodian) { var config = custodianAccount.GetBlob(); var tradableAssetPairs = tradingCustodian.GetTradableAssetPairs(); foreach (var pair in assetBalances) { var asset = pair.Key; var assetBalance = assetBalances[asset]; if (asset.Equals(defaultCurrency)) { assetBalance.FormattedFiatValue = _currencyNameTable.DisplayFormatCurrency(pair.Value.Qty, defaultCurrency); } else { try { var quote = await tradingCustodian.GetQuoteForAssetAsync(defaultCurrency, asset, config, default); assetBalance.Bid = quote.Bid; assetBalance.Ask = quote.Ask; assetBalance.FiatAsset = defaultCurrency; assetBalance.FormattedBid = _currencyNameTable.DisplayFormatCurrency(quote.Bid, quote.FromAsset); assetBalance.FormattedAsk = _currencyNameTable.DisplayFormatCurrency(quote.Ask, quote.FromAsset); assetBalance.FormattedFiatValue = _currencyNameTable.DisplayFormatCurrency(pair.Value.Qty * quote.Bid, pair.Value.FiatAsset); assetBalance.TradableAssetPairs = tradableAssetPairs.Where(o => o.AssetBought == asset || o.AssetSold == asset); } catch (WrongTradingPairException) { // Cannot trade this asset, just ignore } } } } if (custodian is ICanWithdraw withdrawableCustodian) { var withdrawableePaymentMethods = withdrawableCustodian.GetWithdrawablePaymentMethods(); foreach (var withdrawableePaymentMethod in withdrawableePaymentMethods) { var withdrawableAsset = withdrawableePaymentMethod.Split("-")[0]; if (assetBalances.ContainsKey(withdrawableAsset)) { var assetBalance = assetBalances[withdrawableAsset]; assetBalance.CanWithdraw = true; } } } if (custodian is ICanDeposit depositableCustodian) { var depositablePaymentMethods = depositableCustodian.GetDepositablePaymentMethods(); foreach (var depositablePaymentMethod in depositablePaymentMethods) { var depositableAsset = depositablePaymentMethod.Split("-")[0]; if (assetBalances.ContainsKey(depositableAsset)) { var assetBalance = assetBalances[depositableAsset]; assetBalance.CanDeposit = true; } } } vm.AssetBalances = assetBalances; } catch (Exception e) { vm.GetAssetBalanceException = e; } return(View(vm)); }