2020-06-29 04:44:35 +02:00
|
|
|
using System;
|
2020-06-24 03:34:09 +02:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
2020-06-28 10:55:27 +02:00
|
|
|
using System.Threading;
|
2020-06-24 03:34:09 +02:00
|
|
|
using System.Threading.Tasks;
|
2021-10-22 04:17:40 +02:00
|
|
|
using BTCPayServer.Abstractions.Constants;
|
2020-11-17 13:46:23 +01:00
|
|
|
using BTCPayServer.Abstractions.Extensions;
|
|
|
|
using BTCPayServer.Abstractions.Models;
|
2021-10-22 04:17:40 +02:00
|
|
|
using BTCPayServer.Client;
|
2021-04-13 10:36:49 +02:00
|
|
|
using BTCPayServer.Client.Models;
|
2020-06-24 03:34:09 +02:00
|
|
|
using BTCPayServer.Data;
|
2020-06-28 10:55:27 +02:00
|
|
|
using BTCPayServer.HostedServices;
|
2020-06-24 03:34:09 +02:00
|
|
|
using BTCPayServer.Models;
|
2020-06-28 10:55:27 +02:00
|
|
|
using BTCPayServer.Models.WalletViewModels;
|
|
|
|
using BTCPayServer.Payments;
|
|
|
|
using BTCPayServer.Rating;
|
2021-10-22 04:17:40 +02:00
|
|
|
using BTCPayServer.Services;
|
|
|
|
using BTCPayServer.Services.Rates;
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2020-06-28 10:55:27 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2021-10-18 05:37:59 +02:00
|
|
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
2020-06-28 10:55:27 +02:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
2021-04-13 10:36:49 +02:00
|
|
|
using PayoutData = BTCPayServer.Data.PayoutData;
|
2021-10-22 04:17:40 +02:00
|
|
|
using StoreData = BTCPayServer.Data.StoreData;
|
2020-06-24 03:34:09 +02:00
|
|
|
|
|
|
|
namespace BTCPayServer.Controllers
|
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
[Route("stores/{storeId}/pull-payments")]
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-10-22 04:17:40 +02:00
|
|
|
[AutoValidateAntiforgeryToken]
|
2022-01-07 04:32:00 +01:00
|
|
|
public class UIStorePullPaymentsController : Controller
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
|
|
|
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
|
|
|
|
private readonly CurrencyNameTable _currencyNameTable;
|
|
|
|
private readonly PullPaymentHostedService _pullPaymentService;
|
|
|
|
private readonly ApplicationDbContextFactory _dbContextFactory;
|
|
|
|
private readonly BTCPayNetworkJsonSerializerSettings _jsonSerializerSettings;
|
|
|
|
|
|
|
|
public StoreData CurrentStore
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
get
|
|
|
|
{
|
|
|
|
return HttpContext.GetStoreData();
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 04:32:00 +01:00
|
|
|
public UIStorePullPaymentsController(BTCPayNetworkProvider btcPayNetworkProvider,
|
2021-12-31 08:59:02 +01:00
|
|
|
IEnumerable<IPayoutHandler> payoutHandlers,
|
|
|
|
CurrencyNameTable currencyNameTable,
|
2021-10-22 04:17:40 +02:00
|
|
|
PullPaymentHostedService pullPaymentHostedService,
|
|
|
|
ApplicationDbContextFactory dbContextFactory,
|
2021-12-31 08:59:02 +01:00
|
|
|
BTCPayNetworkJsonSerializerSettings jsonSerializerSettings)
|
2021-10-22 04:17:40 +02:00
|
|
|
{
|
|
|
|
_btcPayNetworkProvider = btcPayNetworkProvider;
|
|
|
|
_payoutHandlers = payoutHandlers;
|
|
|
|
_currencyNameTable = currencyNameTable;
|
|
|
|
_pullPaymentService = pullPaymentHostedService;
|
|
|
|
_dbContextFactory = dbContextFactory;
|
|
|
|
_jsonSerializerSettings = jsonSerializerSettings;
|
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpGet("new")]
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-11-04 08:21:01 +01:00
|
|
|
public async Task<IActionResult> NewPullPayment(string storeId)
|
2021-10-22 04:17:40 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
if (CurrentStore is null)
|
2021-01-16 11:48:05 +01:00
|
|
|
return NotFound();
|
2021-11-04 08:21:01 +01:00
|
|
|
|
2021-11-13 02:09:32 +01:00
|
|
|
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
|
|
|
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
|
|
|
|
});
|
2022-01-26 06:57:35 +01:00
|
|
|
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
|
2021-11-13 02:09:32 +01:00
|
|
|
}
|
2021-01-16 11:48:05 +01:00
|
|
|
return View(new NewPullPaymentModel
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
|
|
|
Name = "",
|
2020-12-08 05:04:50 +01:00
|
|
|
Currency = "BTC",
|
|
|
|
CustomCSSLink = "",
|
|
|
|
EmbeddedCSS = "",
|
2021-11-13 02:09:32 +01:00
|
|
|
PaymentMethodItems = paymentMethods.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true))
|
2020-06-24 03:34:09 +02:00
|
|
|
});
|
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpPost("new")]
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-10-22 04:17:40 +02:00
|
|
|
public async Task<IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
if (CurrentStore is null)
|
2021-01-16 11:48:05 +01:00
|
|
|
return NotFound();
|
|
|
|
|
2021-11-04 08:21:01 +01:00
|
|
|
var paymentMethodOptions = await _payoutHandlers.GetSupportedPaymentMethods(CurrentStore);
|
2021-10-18 05:37:59 +02:00
|
|
|
model.PaymentMethodItems =
|
|
|
|
paymentMethodOptions.Select(id => new SelectListItem(id.ToPrettyString(), id.ToString(), true));
|
2020-06-24 03:34:09 +02:00
|
|
|
model.Name ??= string.Empty;
|
2021-11-29 07:26:30 +01:00
|
|
|
model.Currency = model.Currency?.ToUpperInvariant()?.Trim() ?? String.Empty;
|
|
|
|
model.PaymentMethods ??= new List<string>();
|
2021-10-18 05:37:59 +02:00
|
|
|
if (!model.PaymentMethods.Any())
|
|
|
|
{
|
2021-11-29 07:26:30 +01:00
|
|
|
// 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));
|
2021-10-18 05:37:59 +02:00
|
|
|
ModelState.AddModelError(nameof(model.PaymentMethods), "You need at least one payment method");
|
|
|
|
}
|
2021-10-22 04:17:40 +02:00
|
|
|
if (_currencyNameTable.GetCurrencyData(model.Currency, false) is null)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
|
|
|
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.");
|
|
|
|
}
|
2021-10-18 05:37:59 +02:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
2020-06-24 03:34:09 +02:00
|
|
|
if (!ModelState.IsValid)
|
|
|
|
return View(model);
|
|
|
|
await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment()
|
|
|
|
{
|
|
|
|
Name = model.Name,
|
2022-02-10 06:54:00 +01:00
|
|
|
Description = model.Description,
|
2020-06-24 03:34:09 +02:00
|
|
|
Amount = model.Amount,
|
2020-06-24 06:44:26 +02:00
|
|
|
Currency = model.Currency,
|
2021-10-22 04:17:40 +02:00
|
|
|
StoreId = storeId,
|
2021-10-18 05:37:59 +02:00
|
|
|
PaymentMethodIds = selectedPaymentMethodIds,
|
2020-12-08 05:04:50 +01:00
|
|
|
EmbeddedCSS = model.EmbeddedCSS,
|
2022-01-24 12:17:09 +01:00
|
|
|
CustomCSSLink = model.CustomCSSLink,
|
|
|
|
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration)
|
2020-06-24 03:34:09 +02:00
|
|
|
});
|
|
|
|
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Pull payment request created",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
});
|
2021-10-22 04:17:40 +02:00
|
|
|
return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpGet("")]
|
2022-02-17 10:13:28 +01:00
|
|
|
public async Task<IActionResult> PullPayments(
|
|
|
|
string storeId,
|
|
|
|
PullPaymentState pullPaymentState,
|
|
|
|
int skip = 0,
|
|
|
|
int count = 50,
|
|
|
|
string sortOrder = "desc"
|
|
|
|
)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-10-22 08:10:59 +02:00
|
|
|
await using var ctx = _dbContextFactory.CreateContext();
|
2020-06-24 03:34:09 +02:00
|
|
|
var now = DateTimeOffset.UtcNow;
|
2021-10-22 08:10:59 +02:00
|
|
|
var ppsQuery = ctx.PullPayments
|
|
|
|
.Include(data => data.Payouts)
|
2022-02-17 10:13:28 +01:00
|
|
|
.Where(p => p.StoreId == storeId);
|
2021-01-16 11:48:05 +01:00
|
|
|
|
2021-10-22 08:10:59 +02:00
|
|
|
if (sortOrder != null)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-10-22 08:10:59 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-17 10:13:28 +01:00
|
|
|
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("PaymentMethods", "Stores", new { storeId });
|
|
|
|
}
|
|
|
|
|
2022-02-24 12:49:16 +01:00
|
|
|
var vm = this.ParseListQuery(new PullPaymentsModel
|
2021-10-22 08:10:59 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
Skip = skip,
|
|
|
|
Count = count,
|
2022-02-17 10:13:28 +01:00
|
|
|
Total = await ppsQuery.CountAsync(),
|
|
|
|
ActiveState = pullPaymentState
|
2021-10-22 08:10:59 +02:00
|
|
|
});
|
2022-02-17 10:13:28 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-10-22 08:10:59 +02:00
|
|
|
var pps = (await ppsQuery
|
|
|
|
.Skip(vm.Skip)
|
|
|
|
.Take(vm.Count)
|
|
|
|
.ToListAsync()
|
|
|
|
);
|
|
|
|
foreach (var pp in pps)
|
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
var totalCompleted = pp.Payouts.Where(p => (p.State == PayoutState.Completed ||
|
|
|
|
p.State == PayoutState.InProgress) && p.IsInPeriod(pp, now))
|
2020-06-24 03:34:09 +02:00
|
|
|
.Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
2021-10-22 08:10:59 +02:00
|
|
|
var totalAwaiting = pp.Payouts.Where(p => (p.State == PayoutState.AwaitingPayment ||
|
|
|
|
p.State == PayoutState.AwaitingApproval) &&
|
2021-12-31 08:59:02 +01:00
|
|
|
p.IsInPeriod(pp, now)).Select(o => o.GetBlob(_jsonSerializerSettings).Amount).Sum();
|
|
|
|
;
|
2020-06-24 03:34:09 +02:00
|
|
|
var ppBlob = pp.GetBlob();
|
2021-10-22 04:17:40 +02:00
|
|
|
var ni = _currencyNameTable.GetCurrencyData(ppBlob.Currency, true);
|
|
|
|
var nfi = _currencyNameTable.GetNumberFormatInfo(ppBlob.Currency, true);
|
2020-06-24 03:34:09 +02:00
|
|
|
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),
|
2021-10-22 04:17:40 +02:00
|
|
|
Limit = _currencyNameTable.DisplayFormatCurrency(ppBlob.Limit, ppBlob.Currency),
|
2020-06-24 03:34:09 +02:00
|
|
|
ResetIn = period?.End is DateTimeOffset nr ? ZeroIfNegative(nr - now).TimeString() : null,
|
2022-02-17 10:13:28 +01:00
|
|
|
EndIn = pp.EndDate is DateTimeOffset end ? ZeroIfNegative(end - now).TimeString() : null,
|
|
|
|
},
|
|
|
|
Archived = pp.Archived
|
2020-06-24 03:34:09 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return View(vm);
|
|
|
|
}
|
|
|
|
public TimeSpan ZeroIfNegative(TimeSpan time)
|
|
|
|
{
|
|
|
|
if (time < TimeSpan.Zero)
|
|
|
|
time = TimeSpan.Zero;
|
|
|
|
return time;
|
|
|
|
}
|
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpGet("{pullPaymentId}/archive")]
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-10-22 04:17:40 +02:00
|
|
|
public IActionResult ArchivePullPayment(string storeId,
|
2020-06-24 03:34:09 +02:00
|
|
|
string pullPaymentId)
|
|
|
|
{
|
2021-09-07 04:55:53 +02:00
|
|
|
return View("Confirm", new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"));
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpPost("{pullPaymentId}/archive")]
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-10-22 04:17:40 +02:00
|
|
|
public async Task<IActionResult> ArchivePullPaymentPost(string storeId,
|
2020-06-24 03:34:09 +02:00
|
|
|
string pullPaymentId)
|
|
|
|
{
|
|
|
|
await _pullPaymentService.Cancel(new HostedServices.PullPaymentHostedService.CancelRequest(pullPaymentId));
|
2022-02-24 12:49:16 +01:00
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
|
|
|
Message = "Pull payment archived",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
});
|
2022-02-24 12:49:16 +01:00
|
|
|
return RedirectToAction(nameof(PullPayments), new { storeId });
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
|
|
|
|
2021-12-07 16:40:24 +01:00
|
|
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpPost("payouts")]
|
2020-06-24 03:34:09 +02:00
|
|
|
public async Task<IActionResult> PayoutsPost(
|
2021-10-22 04:17:40 +02:00
|
|
|
string storeId, PayoutsModel vm, CancellationToken cancellationToken)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
if (vm is null)
|
2020-06-24 03:34:09 +02:00
|
|
|
return NotFound();
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-11-04 08:21:01 +01:00
|
|
|
vm.PaymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
2021-10-18 05:37:59 +02:00
|
|
|
var paymentMethodId = PaymentMethodId.Parse(vm.PaymentMethodId);
|
|
|
|
var handler = _payoutHandlers
|
2021-10-18 08:00:38 +02:00
|
|
|
.FindPayoutHandler(paymentMethodId);
|
2021-04-13 10:36:49 +02:00
|
|
|
var commandState = Enum.Parse<PayoutState>(vm.Command.Split("-").First());
|
|
|
|
var payoutIds = vm.GetSelectedPayouts(commandState);
|
2020-06-24 03:34:09 +02:00
|
|
|
if (payoutIds.Length == 0)
|
|
|
|
{
|
2021-10-18 05:37:59 +02:00
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
|
|
|
Message = "No payout selected",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
|
|
|
return RedirectToAction(nameof(Payouts), new
|
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
storeId = storeId,
|
2021-10-18 05:37:59 +02:00
|
|
|
pullPaymentId = vm.PullPaymentId,
|
|
|
|
paymentMethodId = paymentMethodId.ToString()
|
2020-06-24 03:34:09 +02:00
|
|
|
});
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
var command = vm.Command.Substring(vm.Command.IndexOf('-', StringComparison.InvariantCulture) + 1);
|
2021-08-05 07:47:25 +02:00
|
|
|
if (handler != null)
|
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
var result = await handler.DoSpecificAction(command, payoutIds, storeId);
|
2021-08-05 07:47:25 +02:00
|
|
|
if (result != null)
|
|
|
|
{
|
|
|
|
TempData.SetStatusMessageModel(result);
|
|
|
|
}
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
switch (command)
|
|
|
|
{
|
|
|
|
case "approve-pay":
|
|
|
|
case "approve":
|
2020-06-24 06:44:26 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
await using var ctx = this._dbContextFactory.CreateContext();
|
|
|
|
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
|
|
|
var payouts = await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
|
|
|
|
|
|
|
var failed = false;
|
|
|
|
for (int i = 0; i < payouts.Count; i++)
|
2020-06-24 06:44:26 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
var payout = payouts[i];
|
|
|
|
if (payout.State != PayoutState.AwaitingApproval)
|
|
|
|
continue;
|
|
|
|
var rateResult = await _pullPaymentService.GetRate(payout, null, cancellationToken);
|
|
|
|
if (rateResult.BidAsk == null)
|
|
|
|
{
|
|
|
|
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = $"Rate unavailable: {rateResult.EvaluatedRule}",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
|
|
|
failed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var approveResult = await _pullPaymentService.Approve(new HostedServices.PullPaymentHostedService.PayoutApproval()
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
PayoutId = payout.Id,
|
|
|
|
Revision = payout.GetBlob(_jsonSerializerSettings).Revision,
|
|
|
|
Rate = rateResult.BidAsk.Ask
|
2021-04-13 10:36:49 +02:00
|
|
|
});
|
2021-12-31 08:59:02 +01:00
|
|
|
if (approveResult != PullPaymentHostedService.PayoutApproval.Result.Ok)
|
|
|
|
{
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = PullPaymentHostedService.PayoutApproval.GetErrorMessage(approveResult),
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
|
|
|
failed = true;
|
|
|
|
break;
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
|
|
|
if (failed)
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-10-18 05:37:59 +02:00
|
|
|
break;
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
if (command == "approve-pay")
|
|
|
|
{
|
|
|
|
goto case "pay";
|
|
|
|
}
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Payouts approved",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
});
|
2021-10-18 05:37:59 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
|
|
|
|
case "pay":
|
2021-07-16 09:57:37 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
if (handler is { })
|
|
|
|
return await handler?.InitiatePayment(paymentMethodId, payoutIds);
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Paying via this payment method is not supported",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2021-04-13 10:36:49 +02:00
|
|
|
|
2021-06-10 11:43:45 +02:00
|
|
|
case "mark-paid":
|
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
await using var ctx = this._dbContextFactory.CreateContext();
|
|
|
|
ctx.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
|
|
|
var payouts = await GetPayoutsForPaymentMethod(paymentMethodId, ctx, payoutIds, storeId, cancellationToken);
|
|
|
|
for (int i = 0; i < payouts.Count; i++)
|
2021-06-10 11:43:45 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
var payout = payouts[i];
|
|
|
|
if (payout.State != PayoutState.AwaitingPayment)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
var result = await _pullPaymentService.MarkPaid(new PayoutPaidRequest()
|
2021-06-10 11:43:45 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
PayoutId = payout.Id
|
2021-06-10 11:43:45 +02:00
|
|
|
});
|
2021-12-31 08:59:02 +01:00
|
|
|
if (result != PayoutPaidRequest.PayoutPaidResult.Ok)
|
2021-06-10 11:43:45 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = PayoutPaidRequest.GetErrorMessage(result),
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
|
|
|
return RedirectToAction(nameof(Payouts), new
|
|
|
|
{
|
|
|
|
storeId = storeId,
|
|
|
|
pullPaymentId = vm.PullPaymentId,
|
|
|
|
paymentMethodId = paymentMethodId.ToString()
|
|
|
|
});
|
|
|
|
}
|
2021-06-10 11:43:45 +02:00
|
|
|
}
|
|
|
|
|
2021-12-31 08:59:02 +01:00
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
|
|
|
{
|
|
|
|
Message = "Payouts marked as paid",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
2021-06-10 11:43:45 +02:00
|
|
|
|
2021-04-13 10:36:49 +02:00
|
|
|
case "cancel":
|
|
|
|
await _pullPaymentService.Cancel(
|
2021-10-18 05:37:59 +02:00
|
|
|
new PullPaymentHostedService.CancelRequest(payoutIds));
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
2021-04-13 10:36:49 +02:00
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
Message = "Payouts archived",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Success
|
2021-04-13 10:36:49 +02:00
|
|
|
});
|
2021-08-05 07:47:25 +02:00
|
|
|
break;
|
2021-07-16 09:57:37 +02:00
|
|
|
}
|
2021-10-18 05:37:59 +02:00
|
|
|
|
2021-08-05 07:47:25 +02:00
|
|
|
return RedirectToAction(nameof(Payouts),
|
2021-10-18 05:37:59 +02:00
|
|
|
new
|
|
|
|
{
|
2021-10-22 04:17:40 +02:00
|
|
|
storeId = storeId,
|
2021-10-18 05:37:59 +02:00
|
|
|
pullPaymentId = vm.PullPaymentId,
|
|
|
|
paymentMethodId = paymentMethodId.ToString()
|
|
|
|
});
|
2021-04-13 10:36:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private static async Task<List<PayoutData>> GetPayoutsForPaymentMethod(PaymentMethodId paymentMethodId,
|
|
|
|
ApplicationDbContext ctx, string[] payoutIds,
|
|
|
|
string storeId, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
var payouts = (await ctx.Payouts
|
|
|
|
.Include(p => p.PullPaymentData)
|
|
|
|
.Include(p => p.PullPaymentData.StoreData)
|
|
|
|
.Where(p => payoutIds.Contains(p.Id))
|
|
|
|
.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived)
|
|
|
|
.ToListAsync(cancellationToken))
|
|
|
|
.Where(p => p.GetPaymentMethodId() == paymentMethodId)
|
|
|
|
.ToList();
|
|
|
|
return payouts;
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
|
|
|
|
2021-10-22 04:17:40 +02:00
|
|
|
[HttpGet("payouts")]
|
2020-06-24 03:34:09 +02:00
|
|
|
public async Task<IActionResult> Payouts(
|
2021-10-22 04:17:40 +02:00
|
|
|
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
|
2021-06-30 09:59:01 +02:00
|
|
|
int skip = 0, int count = 50)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-11-04 08:21:01 +01:00
|
|
|
var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData());
|
2021-11-13 02:09:32 +01:00
|
|
|
if (!paymentMethods.Any())
|
|
|
|
{
|
|
|
|
TempData.SetStatusMessageModel(new StatusMessageModel
|
|
|
|
{
|
|
|
|
Message = "You must enable at least one payment method before creating a payout.",
|
|
|
|
Severity = StatusMessageModel.StatusSeverity.Error
|
|
|
|
});
|
2022-01-26 06:57:35 +01:00
|
|
|
return RedirectToAction(nameof(UIStoresController.GeneralSettings), "UIStores", new { storeId });
|
2021-11-13 02:09:32 +01:00
|
|
|
}
|
|
|
|
|
2021-06-30 09:59:01 +02:00
|
|
|
var vm = this.ParseListQuery(new PayoutsModel
|
|
|
|
{
|
2021-11-04 08:21:01 +01:00
|
|
|
PaymentMethods = paymentMethods,
|
2021-12-31 08:59:02 +01:00
|
|
|
PaymentMethodId = paymentMethodId ?? paymentMethods.First().ToString(),
|
|
|
|
PullPaymentId = pullPaymentId,
|
|
|
|
PayoutState = payoutState,
|
2021-06-30 09:59:01 +02:00
|
|
|
Skip = skip,
|
|
|
|
Count = count
|
|
|
|
});
|
|
|
|
vm.Payouts = new List<PayoutsModel.PayoutModel>();
|
|
|
|
await using var ctx = _dbContextFactory.CreateContext();
|
2020-06-24 03:34:09 +02:00
|
|
|
var payoutRequest = ctx.Payouts.Where(p => p.PullPaymentData.StoreId == storeId && !p.PullPaymentData.Archived);
|
2021-10-18 05:37:59 +02:00
|
|
|
if (pullPaymentId != null)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
|
|
|
payoutRequest = payoutRequest.Where(p => p.PullPaymentDataId == vm.PullPaymentId);
|
2021-12-31 08:59:02 +01:00
|
|
|
vm.PullPaymentName = (await ctx.PullPayments.FindAsync(pullPaymentId)).GetBlob().Name;
|
2021-06-30 09:59:01 +02:00
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-06-30 09:59:01 +02:00
|
|
|
if (vm.PaymentMethodId != null)
|
|
|
|
{
|
2021-10-18 05:37:59 +02:00
|
|
|
var pmiStr = vm.PaymentMethodId;
|
2021-06-30 09:59:01 +02:00
|
|
|
payoutRequest = payoutRequest.Where(p => p.PaymentMethodId == pmiStr);
|
|
|
|
}
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2021-06-30 09:59:01 +02:00
|
|
|
vm.PayoutStateCount = payoutRequest.GroupBy(data => data.State)
|
2021-12-31 08:59:02 +01:00
|
|
|
.Select(e => new { e.Key, Count = e.Count() })
|
2021-06-30 09:59:01 +02:00
|
|
|
.ToDictionary(arg => arg.Key, arg => arg.Count);
|
|
|
|
foreach (PayoutState value in Enum.GetValues(typeof(PayoutState)))
|
|
|
|
{
|
2021-12-31 08:59:02 +01:00
|
|
|
if (vm.PayoutStateCount.ContainsKey(value))
|
2021-06-30 09:59:01 +02:00
|
|
|
continue;
|
|
|
|
vm.PayoutStateCount.Add(value, 0);
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
2021-06-30 09:59:01 +02:00
|
|
|
|
|
|
|
vm.PayoutStateCount = vm.PayoutStateCount.OrderBy(pair => pair.Key)
|
|
|
|
.ToDictionary(pair => pair.Key, pair => pair.Value);
|
|
|
|
|
|
|
|
payoutRequest = payoutRequest.Where(p => p.State == vm.PayoutState);
|
2021-12-31 08:59:02 +01:00
|
|
|
vm.Total = await payoutRequest.CountAsync();
|
2021-06-30 09:59:01 +02:00
|
|
|
payoutRequest = payoutRequest.Skip(vm.Skip).Take(vm.Count);
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2020-06-24 03:34:09 +02:00
|
|
|
var payouts = await payoutRequest.OrderByDescending(p => p.Date)
|
2020-06-28 10:55:27 +02:00
|
|
|
.Select(o => new
|
|
|
|
{
|
2020-06-24 03:34:09 +02:00
|
|
|
Payout = o,
|
|
|
|
PullPayment = o.PullPaymentData
|
|
|
|
}).ToListAsync();
|
2021-06-30 09:59:01 +02:00
|
|
|
foreach (var item in payouts)
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-06-30 09:59:01 +02:00
|
|
|
var ppBlob = item.PullPayment.GetBlob();
|
|
|
|
var payoutBlob = item.Payout.GetBlob(_jsonSerializerSettings);
|
|
|
|
var m = new PayoutsModel.PayoutModel
|
2020-06-24 03:34:09 +02:00
|
|
|
{
|
2021-06-30 09:59:01 +02:00
|
|
|
PullPaymentId = item.PullPayment.Id,
|
|
|
|
PullPaymentName = ppBlob.Name ?? item.PullPayment.Id,
|
|
|
|
Date = item.Payout.Date,
|
|
|
|
PayoutId = item.Payout.Id,
|
2021-10-22 04:17:40 +02:00
|
|
|
Amount = _currencyNameTable.DisplayFormatCurrency(payoutBlob.Amount, ppBlob.Currency),
|
2021-06-30 09:59:01 +02:00
|
|
|
Destination = payoutBlob.Destination
|
|
|
|
};
|
|
|
|
var handler = _payoutHandlers
|
2021-10-18 08:00:38 +02:00
|
|
|
.FindPayoutHandler(item.Payout.GetPaymentMethodId());
|
2021-06-30 09:59:01 +02:00
|
|
|
var proofBlob = handler?.ParseProof(item.Payout);
|
|
|
|
m.ProofLink = proofBlob?.Link;
|
|
|
|
vm.Payouts.Add(m);
|
2020-06-24 03:34:09 +02:00
|
|
|
}
|
|
|
|
return View(vm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|