From 32c5ee14f5cb137fdc7103bff38483d5ddd839b5 Mon Sep 17 00:00:00 2001 From: Kukks Date: Tue, 21 Nov 2023 12:26:48 +0100 Subject: [PATCH] add voucher poc --- BTCPayServerPlugins.sln | 10 + .../BTCPayServer.Plugins.Vouchers.csproj | 42 ++++ .../BTCPayServer.Plugins.Vouchers/Program.cs | 22 ++ .../Views/Shared/VoucherNav.cshtml | 34 +++ .../Views/Voucher/CreateVoucher.cshtml | 40 ++++ .../Views/Voucher/ListVouchers.cshtml | 140 +++++++++++ .../Views/Voucher/View.cshtml | 87 +++++++ .../VoucherController.cs | 218 ++++++++++++++++++ .../_ViewImports.cshtml | 5 + .../WabisabiPlugin.cs | 99 ++++---- .../WabisabiService.cs | 128 +++++----- 11 files changed, 725 insertions(+), 100 deletions(-) create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/BTCPayServer.Plugins.Vouchers.csproj create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/Program.cs create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/Views/Shared/VoucherNav.cshtml create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/CreateVoucher.cshtml create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/ListVouchers.cshtml create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/View.cshtml create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs create mode 100644 Plugins/BTCPayServer.Plugins.Vouchers/_ViewImports.cshtml diff --git a/BTCPayServerPlugins.sln b/BTCPayServerPlugins.sln index 9a59434..0d74a68 100644 --- a/BTCPayServerPlugins.sln +++ b/BTCPayServerPlugins.sln @@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.FileSe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DynamicReports", "Plugins\BTCPayServer.Plugins.DynamicReports\BTCPayServer.Plugins.DynamicReports.csproj", "{BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Vouchers", "Plugins\BTCPayServer.Plugins.Vouchers\BTCPayServer.Plugins.Vouchers.csproj", "{ED061A37-488F-429C-A291-6A5188A47443}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -253,6 +255,14 @@ Global {BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU {BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU {BCB4E68D-089F-481E-A3AE-FC9CED6AA34D}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Release|Any CPU.Build.0 = Release|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU + {ED061A37-488F-429C-A291-6A5188A47443}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962} diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/BTCPayServer.Plugins.Vouchers.csproj b/Plugins/BTCPayServer.Plugins.Vouchers/BTCPayServer.Plugins.Vouchers.csproj new file mode 100644 index 0000000..a4ae82e --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/BTCPayServer.Plugins.Vouchers.csproj @@ -0,0 +1,42 @@ + + + + net6.0 + 10 + + + + + Vouchers + Allows you to give users Bitcoin vouchers. + 1.0.0 + + + + true + false + true + + + + + + StaticWebAssetsEnabled=false + false + runtime;native;build;buildTransitive;contentFiles + + + + + + + + + + + + + + + + diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/Program.cs b/Plugins/BTCPayServer.Plugins.Vouchers/Program.cs new file mode 100644 index 0000000..05e0c31 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/Program.cs @@ -0,0 +1,22 @@ +using BTCPayServer.Abstractions.Contracts; +using BTCPayServer.Abstractions.Models; +using BTCPayServer.Abstractions.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace BTCPayServer.Plugins.Vouchers +{ + public class VoucherPlugin : BaseBTCPayServerPlugin + { + public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = + { + new() {Identifier = nameof(BTCPayServer), Condition = ">=1.11.0"} + }; + + public override void Execute(IServiceCollection applicationBuilder) + { + applicationBuilder.AddSingleton(new UIExtension("VoucherNav", + "store-integrations-nav")); + base.Execute(applicationBuilder); + } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/Views/Shared/VoucherNav.cshtml b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Shared/VoucherNav.cshtml new file mode 100644 index 0000000..ba797a3 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Shared/VoucherNav.cshtml @@ -0,0 +1,34 @@ +@using BTCPayServer.Abstractions.Contracts +@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.Client +@using Microsoft.AspNetCore.Mvc.TagHelpers +@inject IScopeProvider ScopeProvider +@{ + var storeId = ScopeProvider.GetCurrentStoreId(); +} +@if (!string.IsNullOrEmpty(storeId)) +{ + +} diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/CreateVoucher.cshtml b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/CreateVoucher.cshtml new file mode 100644 index 0000000..64e0ca1 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/CreateVoucher.cshtml @@ -0,0 +1,40 @@ +@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.TagHelpers +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer +@using BTCPayServer.Data +@model List +@{ + ViewData.SetActivePage("Voucher", "Create Voucher", "Voucher"); + +} + +
+ +
+ + + + + +
+
+
+
+ + +
+
+ + +
+
+
+
+ \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/ListVouchers.cshtml b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/ListVouchers.cshtml new file mode 100644 index 0000000..5b8360b --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/ListVouchers.cshtml @@ -0,0 +1,140 @@ +@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.Abstractions.Contracts +@using BTCPayServer.Abstractions.Models +@using BTCPayServer.Client +@using BTCPayServer.Services +@inject IScopeProvider ScopeProvider +@inject DisplayFormatter DisplayFormatter +@model List +@{ + ViewData.SetActivePage("Voucher", "Voucher", "Voucher"); + + var storeId = ScopeProvider.GetCurrentStoreId(); +} + +
+ +
+ + + + + + + @if (Model.Any()) + { + @foreach (var pp in Model) + { + + } + +
+ + + + + + + + + + + @foreach (var pp in Model) + { + + + + + + + + } + +
NameAmountProgressActions
+ + @pp.Name + + @DisplayFormatter.Currency(pp.Amount, pp.Currency)@string.Join(", ", pp.PaymentMethods.Select(id => id.ToPrettyString())) +
+
+
+
+
+
+
+ + Archive + + } + - + + Full view + + - + + Print view + +
+
+ + + + @section PageFootContent { + + } + } + else + { +

+ There are no active vouchers. +

+ } + + + \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/View.cshtml b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/View.cshtml new file mode 100644 index 0000000..d1ab20f --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/Views/Voucher/View.cshtml @@ -0,0 +1,87 @@ +@model BTCPayServer.Plugins.Vouchers.VoucherController.VoucherViewModel +@using BTCPayServer.Components.QRCode +@using BTCPayServer.Services +@inject BTCPayServerEnvironment Env +@inject DisplayFormatter DisplayFormatter +@{ + Layout = null; + ViewData["Title"] = Model.Name; + + string lnurl = null; + if (Model.SupportsLNURL) + { + lnurl = LNURL.LNURL.EncodeBech32(new Uri(Url.Action("GetLNURLForPullPayment", "UILNURL", new {cryptoCode = "BTC", pullPaymentId = Model.Id}, Context.Request.Scheme, Context.Request.Host.ToString()))); + } + + var fullView = Url.Action("ViewPullPayment", "UIPullPayment", new {pullPaymentId = Model.Id}, Context.Request.Scheme, Context.Request.Host.ToString()); +} + + + + + + + + + + +
+
+
+ + +
+ +

Voucher

+
+ + @if (lnurl != null) + { + +

Scan with a Bitcoin Lightning wallet to redeem your voucher

+ } + + + + +

Scan or open this link in your browser for the full option list for redemption

+ +
+
+
+ +
Amount
+
+
@DisplayFormatter.Currency(Model.Amount, Model.Currency, DisplayFormatter.CurrencyFormat.Symbol)
+
+ @if (!string.IsNullOrEmpty(Model.Description)) + { +
+ @Model.Description +
+ } +
+ +
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs b/Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs new file mode 100644 index 0000000..ba7d89b --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/VoucherController.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; +using BTCPayServer.Client.Models; +using BTCPayServer.Controllers; +using BTCPayServer.Data; +using BTCPayServer.HostedServices; +using BTCPayServer.Models.WalletViewModels; +using BTCPayServer.Payments; +using BTCPayServer.Rating; +using BTCPayServer.Services.Rates; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using NBitcoin; +using NBitcoin.DataEncoders; + +namespace BTCPayServer.Plugins.Vouchers; + + + +public class VoucherController : Controller +{ + private readonly PullPaymentHostedService _pullPaymentHostedService; + private readonly UIStorePullPaymentsController _uiStorePullPaymentsController; + private readonly ApplicationDbContextFactory _dbContextFactory; + private readonly IEnumerable _payoutHandlers; + private readonly StoreRepository _storeRepository; + private readonly RateFetcher _rateFetcher; + private readonly BTCPayNetworkProvider _networkProvider; + + public VoucherController(PullPaymentHostedService pullPaymentHostedService, + UIStorePullPaymentsController uiStorePullPaymentsController, + ApplicationDbContextFactory dbContextFactory, + IEnumerable< IPayoutHandler> payoutHandlers, StoreRepository storeRepository, RateFetcher rateFetcher, BTCPayNetworkProvider networkProvider ) + { + _pullPaymentHostedService = pullPaymentHostedService; + _uiStorePullPaymentsController = uiStorePullPaymentsController; + _dbContextFactory = dbContextFactory; + _payoutHandlers = payoutHandlers; + _storeRepository = storeRepository; + _rateFetcher = rateFetcher; + _networkProvider = networkProvider; + } + + [HttpGet("~/plugins/{storeId}/vouchers")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task ListVouchers(string storeId) + { + + var now = DateTimeOffset.UtcNow; + await using var ctx = _dbContextFactory.CreateContext(); + var ppsQuery = await ctx.PullPayments + .Include(data => data.Payouts) + .Where(p => p.StoreId == storeId && p.Archived == false) + .OrderByDescending(data => data.Id).ToListAsync(); + + var vouchers = ppsQuery.Select(pp => (PullPayment: pp, Blob: pp.GetBlob())).Where(blob => blob.Blob.Name.StartsWith("Voucher")).ToList(); + + 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 voucher.", + Severity = StatusMessageModel.StatusSeverity.Error + }); + return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId}); + } + return View( vouchers.Select(tuple => new VoucherViewModel() + { + Amount = tuple.Blob.Limit, + Currency = tuple.Blob.Currency, + Id = tuple.PullPayment.Id, + Name = tuple.Blob.Name, + PaymentMethods = tuple.Blob.SupportedPaymentMethods, + Progress = _pullPaymentHostedService.CalculatePullPaymentProgress(tuple.PullPayment, now) + }).ToList()); + } + + [HttpGet("~/plugins/{storeId}/vouchers/create")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task CreateVoucher(string storeId) + { + 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 voucher.", + Severity = StatusMessageModel.StatusSeverity.Error + }); + return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId}); + } + return View(); + } + + [HttpPost("~/plugins/{storeId}/vouchers/create")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + public async Task CreateVoucher(string storeId, decimal amount, string currency) + { + ModelState.Clear(); + var paymentMethods = await _payoutHandlers.GetSupportedPaymentMethods(HttpContext.GetStoreData()); + if (!paymentMethods.Any()) + { + + TempData[WellKnownTempData.ErrorMessage] = "You must enable at least one payment method before creating a voucher."; + + return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new {storeId}); + } + + if (amount <= 0) + { + TempData[WellKnownTempData.ErrorMessage] = "Amount must be greater than 0"; + return View(); + } + + var storeBLob = HttpContext.GetStoreData().GetStoreBlob(); + currency??=storeBLob.DefaultCurrency; + + var rate = await _rateFetcher.FetchRate(new CurrencyPair("BTC", currency), + storeBLob.GetRateRules(_networkProvider), CancellationToken.None); + if (rate.BidAsk == null) + { + + TempData[WellKnownTempData.ErrorMessage] = "Currency is not supported"; + return View(); + } + + string description = string.Empty; + if (currency!= "BTC") + { + description = $"{amount} {currency} voucher redeemable for {amount * rate.BidAsk.Bid} BTC"; + } + + var pp = await _pullPaymentHostedService.CreatePullPayment(new CreatePullPayment() + { + Amount = amount* rate.BidAsk.Bid, + Currency = "BTC", + Name = "Voucher " + Encoders.Base58.EncodeData(RandomUtils.GetBytes(6)), + Description = description, + StoreId = storeId, + PaymentMethodIds = paymentMethods.ToArray(), + AutoApproveClaims = true + }); + + return RedirectToAction(nameof(View), new {id = pp}); + } + + [HttpGet("~/plugins/vouchers/{id}")] + [AllowAnonymous] + public async Task View(string id) + { + await using var ctx = _dbContextFactory.CreateContext(); + var pp = await ctx.PullPayments + .Include(data => data.Payouts) + .SingleOrDefaultAsync(p => p.Id == id && p.Archived == false); + + if (pp == null) + { + return NotFound(); + } + + var blob = pp.GetBlob(); + if (!blob.Name.StartsWith("Voucher")) + { + return NotFound(); + } + + var now = DateTimeOffset.UtcNow; + var store = await _storeRepository.FindStore(pp.StoreId); + var storeBlob = store.GetStoreBlob(); + var progress = _pullPaymentHostedService.CalculatePullPaymentProgress(pp, now); + return View(new VoucherViewModel() + { + Amount = blob.Limit, + Currency = blob.Currency, + Id = pp.Id, + Name = blob.Name, + PaymentMethods = blob.SupportedPaymentMethods, + Progress = progress, + StoreName = store.StoreName, + BrandColor = storeBlob.BrandColor, + CssFileId = storeBlob.CssFileId, + LogoFileId = storeBlob.LogoFileId, + SupportsLNURL = _pullPaymentHostedService.SupportsLNURL(blob), + Description = blob.Description + }); + } + + + + + public class VoucherViewModel + { + public string Id { get; set; } + public string Name { get; set; } + public decimal Amount { get; set; } + public string Currency { get; set; } + public PaymentMethodId[] PaymentMethods { get; set; } + public PullPaymentsModel.PullPaymentModel.ProgressModel Progress { get; set; } + public string StoreName { get; set; } + public string LogoFileId { get; set; } + public string BrandColor { get; set; } + public string CssFileId { get; set; } + public bool SupportsLNURL { get; set; } + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Vouchers/_ViewImports.cshtml b/Plugins/BTCPayServer.Plugins.Vouchers/_ViewImports.cshtml new file mode 100644 index 0000000..d897d63 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.Vouchers/_ViewImports.cshtml @@ -0,0 +1,5 @@ +@using BTCPayServer.Abstractions.Services +@inject Safe Safe +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, BTCPayServer +@addTagHelper *, BTCPayServer.Abstractions \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs index 9528f69..9ac4ad2 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs @@ -1,14 +1,22 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; using BTCPayServer.Common; +using BTCPayServer.Data; +using BTCPayServer.Payments; +using BTCPayServer.PayoutProcessors; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NBitcoin; using WalletWasabi.Backend.Controllers; @@ -77,7 +85,7 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin applicationBuilder.AddSingleton(new UIExtension("Wabisabi/WabisabiWalletSend", "onchain-wallet-send")); - // applicationBuilder.AddSingleton(); + applicationBuilder.AddSingleton(); Logger.SetMinimumLevel(LogLevel.Info); Logger.SetModes(LogMode.DotNetLoggers); @@ -85,50 +93,51 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin base.Execute(applicationBuilder); } - // public class WabisabiPayoutProcessor: IPayoutProcessorFactory - // { - // private readonly LinkGenerator _linkGenerator; - // - // public WabisabiPayoutProcessor(LinkGenerator linkGenerator) - // { - // _linkGenerator = linkGenerator; - // } - // public string Processor { get; } = "Wabisabi"; - // public string FriendlyName { get; } = "Coinjoin"; - // public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request) - // { - // return _linkGenerator.GetUriByAction( - // nameof(WabisabiStoreController.UpdateWabisabiStoreSettings), - // "WabisabiStore", - // new { storeId}, - // request.Scheme, - // request.Host, - // request.PathBase); - // } - // - // public IEnumerable GetSupportedPaymentMethods() - // { - // return new[] {new PaymentMethodId("BTC", BitcoinPaymentType.Instance)}; - // } - // - // public Task ConstructProcessor(PayoutProcessorData settings) - // { - // return Task.FromResult(new ShellSerice()); - // } - // public class ShellSerice:IHostedService - // { - // public Task StartAsync(CancellationToken cancellationToken) - // { - // return Task.CompletedTask; - // } - // - // public Task StopAsync(CancellationToken cancellationToken) - // { - // return Task.CompletedTask; - // } - // } - // } - // + public class WabisabiPayoutProcessor: IPayoutProcessorFactory + { + private readonly LinkGenerator _linkGenerator; + + public WabisabiPayoutProcessor(LinkGenerator linkGenerator) + { + _linkGenerator = linkGenerator; + } + public string Processor { get; } = "Wabisabi"; + public string FriendlyName { get; } = "Coinjoin"; + public string ConfigureLink(string storeId, PaymentMethodId paymentMethodId, HttpRequest request) + { + return _linkGenerator.GetUriByAction( + nameof(WabisabiStoreController.UpdateWabisabiStoreSettings), + "WabisabiStore", + new { storeId}, + request.Scheme, + request.Host, + request.PathBase); + } + + public IEnumerable GetSupportedPaymentMethods() + { + return new[] {new PaymentMethodId("BTC", BitcoinPaymentType.Instance)}; + } + + public async Task ConstructProcessor(PayoutProcessorData settings) + { + return new ShellSerice(); + } + + public class ShellSerice:IHostedService + { + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } + } + public override void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices) diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs index 80ec78b..c87f50d 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs @@ -7,7 +7,6 @@ using BTCPayServer.Data; using BTCPayServer.PayoutProcessors; using BTCPayServer.Services; using Microsoft.Extensions.Caching.Memory; -using NBitcoin; using Newtonsoft.Json.Linq; namespace BTCPayServer.Plugins.Wabisabi @@ -19,43 +18,52 @@ namespace BTCPayServer.Plugins.Wabisabi private readonly WalletProvider _walletProvider; private readonly WalletRepository _walletRepository; private readonly IMemoryCache _memoryCache; + private readonly PayoutProcessorService _payoutProcessorService; + private readonly EventAggregator _eventAggregator; private string[] _ids => _coordinatorClientInstanceManager.HostedServices.Keys.ToArray(); public WabisabiService(IStoreRepository storeRepository, WabisabiCoordinatorClientInstanceManager coordinatorClientInstanceManager, WalletProvider walletProvider, WalletRepository walletRepository, - IMemoryCache memoryCache) + IMemoryCache memoryCache, PayoutProcessorService payoutProcessorService, EventAggregator eventAggregator) { _storeRepository = storeRepository; _coordinatorClientInstanceManager = coordinatorClientInstanceManager; _walletProvider = walletProvider; _walletRepository = walletRepository; _memoryCache = memoryCache; + _payoutProcessorService = payoutProcessorService; + _eventAggregator = eventAggregator; } public static string GetCacheKey(string storeId) { return $"{nameof(WabisabiStoreSettings)}-{storeId}"; } + public async Task GetWabisabiForStore(string storeId) { - var res = await _memoryCache.GetOrCreate(GetCacheKey(storeId), async entry => { - var res = await _storeRepository.GetSettingAsync(storeId, nameof(WabisabiStoreSettings)); + var res = await _storeRepository.GetSettingAsync(storeId, + nameof(WabisabiStoreSettings)); res ??= new WabisabiStoreSettings(); res.Settings = res.Settings.Where(settings => _ids.Contains(settings.Coordinator)).ToList(); res.Settings.ForEach(settings => { - if(settings.RoundWhenEnabled != null && string.IsNullOrEmpty(settings.RoundWhenEnabled.PlebsDontPayThreshold)) + if (settings.RoundWhenEnabled != null && + string.IsNullOrEmpty(settings.RoundWhenEnabled.PlebsDontPayThreshold)) { settings.RoundWhenEnabled.PlebsDontPayThreshold = "1000000"; } }); + + + await EnablePayoutProcessorBasedOnSettings(storeId, res); return res; }); - + foreach (var wabisabiCoordinatorManager in _coordinatorClientInstanceManager.HostedServices) { if (res.Settings.All(settings => settings.Coordinator != wabisabiCoordinatorManager.Key)) @@ -70,7 +78,32 @@ namespace BTCPayServer.Plugins.Wabisabi return res; } - public async Task SetWabisabiForStore(string storeId, WabisabiStoreSettings wabisabiSettings, string termsCoord = null) + private async Task EnablePayoutProcessorBasedOnSettings(string storeId, WabisabiStoreSettings res) + { + var paybatching = res.Settings.Any(settings => settings.Enabled) && res.Active && (res.PlebMode ||res.BatchPayments); + var existingProcessor = (await _payoutProcessorService.GetProcessors( + new PayoutProcessorService.PayoutProcessorQuery() + { + Stores = new[] {storeId}, + Processors = new[] {"Wabisabi"}, + + })).FirstOrDefault(); + + _eventAggregator.Publish(new PayoutProcessorUpdated() + { + Id = existingProcessor?.Id, + Data = paybatching? new PayoutProcessorData() + { + Id = existingProcessor?.Id, + Processor = "Wabisabi", + StoreId = storeId, + PaymentMethod = "BTC", + }: null + }); + } + + public async Task SetWabisabiForStore(string storeId, WabisabiStoreSettings wabisabiSettings, + string termsCoord = null) { foreach (var setting in wabisabiSettings.Settings.Where(setting => !setting.Enabled)) { @@ -81,54 +114,41 @@ namespace BTCPayServer.Plugins.Wabisabi await _coordinatorClientInstanceManager.StopWallet(wallet, setting.Coordinator); } } - - var res = await GetWabisabiForStore(storeId); - foreach (var wabisabiStoreCoordinatorSettings in wabisabiSettings.Settings) + + var res = await GetWabisabiForStore(storeId); + foreach (var wabisabiStoreCoordinatorSettings in wabisabiSettings.Settings) + { + if (!wabisabiStoreCoordinatorSettings.Enabled) { - if (!wabisabiStoreCoordinatorSettings.Enabled) - { - wabisabiStoreCoordinatorSettings.RoundWhenEnabled = null; - }else if ( - (termsCoord == wabisabiStoreCoordinatorSettings.Coordinator || - res?.Settings?.Find(settings => - settings.Coordinator == wabisabiStoreCoordinatorSettings.Coordinator)?.RoundWhenEnabled is null) && - _coordinatorClientInstanceManager.HostedServices.TryGetValue(wabisabiStoreCoordinatorSettings.Coordinator, out var coordinator)) - { - var round = coordinator.RoundStateUpdater.RoundStates.LastOrDefault(); - wabisabiStoreCoordinatorSettings.RoundWhenEnabled = new LastCoordinatorRoundConfig() - { - CoordinationFeeRate = round.Value.CoinjoinState.Parameters.CoordinationFeeRate.Rate, - PlebsDontPayThreshold = round.Value.CoinjoinState.Parameters.CoordinationFeeRate - .PlebsDontPayThreshold.Satoshi.ToString(), - MinInputCountByRound = round.Value.CoinjoinState.Parameters.MinInputCountByRound, - }; - } - - + wabisabiStoreCoordinatorSettings.RoundWhenEnabled = null; } - await _storeRepository.UpdateSetting(storeId, nameof(WabisabiStoreSettings), wabisabiSettings!); - + else if ( + (termsCoord == wabisabiStoreCoordinatorSettings.Coordinator || + res?.Settings?.Find(settings => + settings.Coordinator == wabisabiStoreCoordinatorSettings.Coordinator) + ?.RoundWhenEnabled is null) && + _coordinatorClientInstanceManager.HostedServices.TryGetValue( + wabisabiStoreCoordinatorSettings.Coordinator, out var coordinator)) + { + var round = coordinator.RoundStateUpdater.RoundStates.LastOrDefault(); + wabisabiStoreCoordinatorSettings.RoundWhenEnabled = new LastCoordinatorRoundConfig() + { + CoordinationFeeRate = round.Value.CoinjoinState.Parameters.CoordinationFeeRate.Rate, + PlebsDontPayThreshold = round.Value.CoinjoinState.Parameters.CoordinationFeeRate + .PlebsDontPayThreshold.Satoshi.ToString(), + MinInputCountByRound = round.Value.CoinjoinState.Parameters.MinInputCountByRound, + }; + } + } + + await _storeRepository.UpdateSetting(storeId, nameof(WabisabiStoreSettings), wabisabiSettings!); + _memoryCache.Remove(GetCacheKey(storeId)); await _walletProvider.SettingsUpdated(storeId, wabisabiSettings); - // var existingProcessor = (await _payoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery() - // { - // Stores = new[] {storeId}, - // Processors = new[] {"Wabisabi"}, - // - // })).FirstOrDefault(); - // _eventAggregator.Publish(new PayoutProcessorUpdated() - // { - // Id = existingProcessor?.Id, - // Data = paybatching? new PayoutProcessorData() - // { - // Id = existingProcessor?.Id, - // Processor = "Wabisabi", - // StoreId = storeId, - // PaymentMethod = "BTC", - // }: null - // }); + + await EnablePayoutProcessorBasedOnSettings(storeId, wabisabiSettings); } - + public async Task> GetCoinjoinHistory(string storeId, bool force = false) { @@ -141,7 +161,7 @@ namespace BTCPayServer.Plugins.Wabisabi return await _memoryCache.GetOrCreateAsync(k, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); - var result = (await _walletRepository.GetWalletObjects( + var result = (await _walletRepository.GetWalletObjects( new GetWalletObjectsQuery(new WalletId(storeId, "BTC")) { Type = "coinjoin" @@ -149,11 +169,9 @@ namespace BTCPayServer.Plugins.Wabisabi .Select(data => JObject.Parse(data.Data).ToObject()) .OrderByDescending(tuple => tuple.Timestamp).ToList(); entry.Value = result; - + return result; }); - } } - -} +} \ No newline at end of file