using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Data.Data; using BTCPayServer.Payments; using BTCPayServer.PayoutProcessors.Settings; using BTCPayServer.Services.Invoices; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.PayoutProcessors.OnChain; public class UIOnChainAutomatedPayoutProcessorsController : Controller { private readonly EventAggregator _eventAggregator; private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly OnChainAutomatedPayoutSenderFactory _onChainAutomatedPayoutSenderFactory; private readonly PayoutProcessorService _payoutProcessorService; public UIOnChainAutomatedPayoutProcessorsController( EventAggregator eventAggregator, BTCPayNetworkProvider btcPayNetworkProvider, OnChainAutomatedPayoutSenderFactory onChainAutomatedPayoutSenderFactory, PayoutProcessorService payoutProcessorService) { _eventAggregator = eventAggregator; _btcPayNetworkProvider = btcPayNetworkProvider; _onChainAutomatedPayoutSenderFactory = onChainAutomatedPayoutSenderFactory; _payoutProcessorService = payoutProcessorService; } [HttpGet("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Configure(string storeId, string cryptoCode) { if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase))) { TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Error, Message = $"This processor cannot handle {cryptoCode}." }); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); } var wallet = HttpContext.GetStoreData().GetDerivationSchemeSettings(_btcPayNetworkProvider, cryptoCode); if (wallet?.IsHotWallet is not true) { TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Error, Message = $"Either your {cryptoCode} wallet is not configured, or it is not a hot wallet. This processor cannot function until a hot wallet is configured in your store." }); } var activeProcessor = (await _payoutProcessorService.GetProcessors( new PayoutProcessorService.PayoutProcessorQuery() { Stores = new[] { storeId }, Processors = new []{ _onChainAutomatedPayoutSenderFactory.Processor}, PaymentMethods = new[] { new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString() } })) .FirstOrDefault(); return View (new OnChainTransferViewModel(activeProcessor is null? new AutomatedPayoutBlob() : OnChainAutomatedPayoutProcessor.GetBlob(activeProcessor))); } [HttpPost("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Configure(string storeId, string cryptoCode, OnChainTransferViewModel automatedTransferBlob) { if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => id.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase))) { TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Error, Message = $"This processor cannot handle {cryptoCode}." }); return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors"); } var activeProcessor = (await _payoutProcessorService.GetProcessors( new PayoutProcessorService.PayoutProcessorQuery() { Stores = new[] { storeId }, Processors = new []{ OnChainAutomatedPayoutSenderFactory.ProcessorName}, PaymentMethods = new[] { new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString() } })) .FirstOrDefault(); activeProcessor ??= new PayoutProcessorData(); activeProcessor.Blob = InvoiceRepository.ToBytes(automatedTransferBlob.ToBlob()); activeProcessor.StoreId = storeId; activeProcessor.PaymentMethod = new PaymentMethodId(cryptoCode, BitcoinPaymentType.Instance).ToString(); activeProcessor.Processor = _onChainAutomatedPayoutSenderFactory.Processor; var tcs = new TaskCompletionSource(); _eventAggregator.Publish(new PayoutProcessorUpdated() { Data = activeProcessor, Id = activeProcessor.Id, Processed = tcs }); TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Success, Message = $"Processor updated." }); await tcs.Task; return RedirectToAction("ConfigureStorePayoutProcessors", "UiPayoutProcessors", new {storeId}); } public class OnChainTransferViewModel { public OnChainTransferViewModel() { } public OnChainTransferViewModel(AutomatedPayoutBlob blob) { IntervalMinutes = blob.Interval.TotalMinutes; } public double IntervalMinutes { get; set; } public AutomatedPayoutBlob ToBlob() { return new AutomatedPayoutBlob() { Interval = TimeSpan.FromMinutes(IntervalMinutes) }; } } }