From f0e013e1f8a92fb5dce8386a7a516608fb6ecf6f Mon Sep 17 00:00:00 2001 From: d11n Date: Wed, 15 Jun 2022 04:32:46 +0200 Subject: [PATCH] Make Pay Button a plugin (#3862) * Move files * Fix potentially missing default payment method Before, it got removed if any other value was changed besides the default payment method. * Fix missing store data * Update BTCPayServer/Plugins/PayButton/PayButtonPlugin.cs Co-authored-by: Pavlenex * Update pay button warning Closes #3535. Co-authored-by: Pavlenex --- BTCPayServer.Tests/SeleniumTests.cs | 1 + BTCPayServer/BTCPayServer.csproj | 6 -- .../Components/MainNav/Default.cshtml | 6 -- .../Controllers/UIPublicController.cs | 1 + .../Controllers/UIStoresController.cs | 68 +----------- .../Controllers/UIPayButtonController.cs | 102 ++++++++++++++++++ .../PayButton/Models}/PayButtonViewModel.cs | 3 +- .../Plugins/PayButton/PayButtonPlugin.cs | 20 ++++ .../Views/Shared/PayButton/Enable.cshtml | 36 +++++++ .../Shared/PayButton/NavExtension.cshtml | 17 +++ .../PayButton}/PayButton.cshtml | 18 +++- .../Shared/PayButton/_ViewImports.cshtml | 4 + .../Views/UIStores/PayButtonEnable.cshtml | 29 ----- BTCPayServer/wwwroot/paybutton/paybutton.js | 6 +- 14 files changed, 199 insertions(+), 118 deletions(-) create mode 100644 BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs rename BTCPayServer/{Models/StoreViewModels => Plugins/PayButton/Models}/PayButtonViewModel.cs (95%) create mode 100644 BTCPayServer/Plugins/PayButton/PayButtonPlugin.cs create mode 100644 BTCPayServer/Views/Shared/PayButton/Enable.cshtml create mode 100644 BTCPayServer/Views/Shared/PayButton/NavExtension.cshtml rename BTCPayServer/Views/{UIStores => Shared/PayButton}/PayButton.cshtml (96%) create mode 100644 BTCPayServer/Views/Shared/PayButton/_ViewImports.cshtml delete mode 100644 BTCPayServer/Views/UIStores/PayButtonEnable.cshtml diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index ab8147166..d4277e150 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -64,6 +64,7 @@ namespace BTCPayServer.Tests Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource); s.Driver.Quit(); } + [Fact(Timeout = TestTimeout)] public async Task CanUseCPFP() { diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 32180db65..a3e2c1ebb 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -182,12 +182,6 @@ $(IncludeRazorContentInPack) - - $(IncludeRazorContentInPack) - - - $(IncludeRazorContentInPack) - $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Components/MainNav/Default.cshtml b/BTCPayServer/Components/MainNav/Default.cshtml index 4f5dd5bd9..a3ac3b5af 100644 --- a/BTCPayServer/Components/MainNav/Default.cshtml +++ b/BTCPayServer/Components/MainNav/Default.cshtml @@ -129,12 +129,6 @@ Payouts - diff --git a/BTCPayServer/Controllers/UIPublicController.cs b/BTCPayServer/Controllers/UIPublicController.cs index 74759e128..4c098f028 100644 --- a/BTCPayServer/Controllers/UIPublicController.cs +++ b/BTCPayServer/Controllers/UIPublicController.cs @@ -6,6 +6,7 @@ using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; +using BTCPayServer.Plugins.PayButton.Models; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs index 91f4b61d9..9973db949 100644 --- a/BTCPayServer/Controllers/UIStoresController.cs +++ b/BTCPayServer/Controllers/UIStoresController.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Data; @@ -61,7 +60,6 @@ namespace BTCPayServer.Controllers AppService appService, WebhookSender webhookNotificationManager, IDataProtectionProvider dataProtector, - NBXplorerDashboard Dashboard, IOptions externalServiceOptions) { _RateFactory = rateFactory; @@ -83,7 +81,6 @@ namespace BTCPayServer.Controllers _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; - _Dashboard = Dashboard; _externalServiceOptions = externalServiceOptions; } @@ -104,7 +101,6 @@ namespace BTCPayServer.Controllers private readonly IAuthorizationService _authorizationService; private readonly AppService _appService; private readonly EventAggregator _EventAggregator; - private readonly NBXplorerDashboard _Dashboard; private readonly IOptions _externalServiceOptions; [TempData] @@ -443,7 +439,7 @@ namespace BTCPayServer.Controllers vm.DefaultPaymentMethod = chosen?.Value; } - PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(Data.StoreData storeData) + public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData) { var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider); @@ -457,7 +453,7 @@ namespace BTCPayServer.Controllers }).ToArray(); } - PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(Data.StoreData storeData) + PaymentMethodOptionViewModel.Format? GetDefaultPaymentMethodChoice(StoreData storeData) { var enabled = storeData.GetEnabledPaymentIds(_NetworkProvider); var defaultPaymentId = storeData.GetDefaultPaymentId(); @@ -991,65 +987,5 @@ namespace BTCPayServer.Controllers return null; return _UserManager.GetUserId(User); } - - [HttpPost("{storeId}/disable-anyone-can-pay")] - public async Task DisableAnyoneCanCreateInvoice(string storeId) - { - var blob = CurrentStore.GetStoreBlob(); - blob.AnyoneCanInvoice = false; - CurrentStore.SetStoreBlob(blob); - TempData[WellKnownTempData.SuccessMessage] = "Feature disabled"; - await _Repo.UpdateStore(CurrentStore); - return RedirectToAction(nameof(PayButton), new { storeId = storeId }); - } - - [Route("{storeId}/paybutton")] - public async Task PayButton() - { - var store = CurrentStore; - var storeBlob = store.GetStoreBlob(); - if (!storeBlob.AnyoneCanInvoice) - { - return View("PayButtonEnable", null); - } - - var apps = await _appService.GetAllApps(_UserManager.GetUserId(User), false, store.Id); - var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash(); - var model = new PayButtonViewModel - { - Price = null, - Currency = storeBlob.DefaultCurrency, - DefaultPaymentMethod = String.Empty, - PaymentMethods = GetEnabledPaymentMethodChoices(store), - ButtonSize = 2, - UrlRoot = appUrl, - PayButtonImageUrl = appUrl + "img/paybutton/pay.svg", - StoreId = store.Id, - ButtonType = 0, - Min = 1, - Max = 20, - Step = "1", - Apps = apps - }; - return View(model); - } - - [HttpPost] - [Route("{storeId}/paybutton")] - public async Task PayButton(bool enableStore) - { - var blob = CurrentStore.GetStoreBlob(); - blob.AnyoneCanInvoice = enableStore; - if (CurrentStore.SetStoreBlob(blob)) - { - await _Repo.UpdateStore(CurrentStore); - TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated"; - } - - return RedirectToAction(nameof(PayButton), new - { - storeId = CurrentStore.Id - }); - } } } diff --git a/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs b/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs new file mode 100644 index 000000000..bcb21121c --- /dev/null +++ b/BTCPayServer/Plugins/PayButton/Controllers/UIPayButtonController.cs @@ -0,0 +1,102 @@ +#nullable enable +using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; +using BTCPayServer.Client; +using BTCPayServer.Controllers; +using BTCPayServer.Data; +using BTCPayServer.Plugins.PayButton.Models; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using StoreData = BTCPayServer.Data.StoreData; + +namespace BTCPayServer.Plugins.PayButton.Controllers +{ + [Route("stores")] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [AutoValidateAntiforgeryToken] + public class UIPayButtonController : Controller + { + public UIPayButtonController( + StoreRepository repo, + UIStoresController storesController, + UserManager userManager, + AppService appService) + { + _repo = repo; + _userManager = userManager; + _appService = appService; + _storesController = storesController; + } + + readonly StoreRepository _repo; + readonly UserManager _userManager; + private readonly AppService _appService; + private readonly UIStoresController _storesController; + + [HttpPost("{storeId}/disable-anyone-can-pay")] + public async Task DisableAnyoneCanCreateInvoice(string storeId) + { + var blob = GetCurrentStore.GetStoreBlob(); + blob.AnyoneCanInvoice = false; + GetCurrentStore.SetStoreBlob(blob); + TempData[WellKnownTempData.SuccessMessage] = "Feature disabled"; + await _repo.UpdateStore(GetCurrentStore); + return RedirectToAction(nameof(PayButton), new { storeId }); + } + + [HttpGet("{storeId}/paybutton")] + public async Task PayButton() + { + var store = GetCurrentStore; + var storeBlob = store.GetStoreBlob(); + if (!storeBlob.AnyoneCanInvoice) + { + return View("PayButton/Enable", null); + } + + var apps = await _appService.GetAllApps(_userManager.GetUserId(User), false, store.Id); + var appUrl = HttpContext.Request.GetAbsoluteRoot().WithTrailingSlash(); + var model = new PayButtonViewModel + { + Price = null, + Currency = storeBlob.DefaultCurrency, + DefaultPaymentMethod = string.Empty, + PaymentMethods = _storesController.GetEnabledPaymentMethodChoices(store), + ButtonSize = 2, + UrlRoot = appUrl, + PayButtonImageUrl = appUrl + "img/paybutton/pay.svg", + StoreId = store.Id, + ButtonType = 0, + Min = 1, + Max = 20, + Step = "1", + Apps = apps + }; + return View("PayButton/PayButton", model); + } + + [HttpPost("{storeId}/paybutton")] + public async Task PayButton(bool enableStore) + { + var blob = GetCurrentStore.GetStoreBlob(); + blob.AnyoneCanInvoice = enableStore; + if (GetCurrentStore.SetStoreBlob(blob)) + { + await _repo.UpdateStore(GetCurrentStore); + TempData[WellKnownTempData.SuccessMessage] = "Store successfully updated"; + } + + return RedirectToAction(nameof(PayButton), new + { + storeId = GetCurrentStore.Id + }); + } + + private StoreData GetCurrentStore => HttpContext.GetStoreData(); + } +} diff --git a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs b/BTCPayServer/Plugins/PayButton/Models/PayButtonViewModel.cs similarity index 95% rename from BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs rename to BTCPayServer/Plugins/PayButton/Models/PayButtonViewModel.cs index 195f72a1b..380dd39f3 100644 --- a/BTCPayServer/Models/StoreViewModels/PayButtonViewModel.cs +++ b/BTCPayServer/Plugins/PayButton/Models/PayButtonViewModel.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using BTCPayServer.ModelBinders; using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Models.StoreViewModels; using Microsoft.AspNetCore.Mvc; -namespace BTCPayServer.Models.StoreViewModels +namespace BTCPayServer.Plugins.PayButton.Models { public class PayButtonViewModel { diff --git a/BTCPayServer/Plugins/PayButton/PayButtonPlugin.cs b/BTCPayServer/Plugins/PayButton/PayButtonPlugin.cs new file mode 100644 index 000000000..bb7a2db4c --- /dev/null +++ b/BTCPayServer/Plugins/PayButton/PayButtonPlugin.cs @@ -0,0 +1,20 @@ +using BTCPayServer.Abstractions.Contracts; +using BTCPayServer.Abstractions.Models; +using BTCPayServer.Abstractions.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace BTCPayServer.Plugins.PayButton +{ + public class PayButtonPlugin : BaseBTCPayServerPlugin + { + public override string Identifier => "BTCPayServer.Plugins.PayButton"; + public override string Name => "Pay Button"; + public override string Description => "Easily-embeddable HTML button for accepting tips and donations ."; + + public override void Execute(IServiceCollection services) + { + services.AddSingleton(new UIExtension("PayButton/NavExtension", "header-nav")); + base.Execute(services); + } + } +} diff --git a/BTCPayServer/Views/Shared/PayButton/Enable.cshtml b/BTCPayServer/Views/Shared/PayButton/Enable.cshtml new file mode 100644 index 000000000..ea120b49d --- /dev/null +++ b/BTCPayServer/Views/Shared/PayButton/Enable.cshtml @@ -0,0 +1,36 @@ +@using BTCPayServer.Views.Stores +@using Microsoft.AspNetCore.Mvc.TagHelpers +@{ + ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id); +} + + +

@ViewData["Title"]

+ +
+
+ +

+ To start using Pay Button, you need to enable this feature explicitly. + Once you do so, anyone could create an invoice on your store (via API, for example). +

+
+ @Html.Hidden("EnableStore", true) + +
+
+
diff --git a/BTCPayServer/Views/Shared/PayButton/NavExtension.cshtml b/BTCPayServer/Views/Shared/PayButton/NavExtension.cshtml new file mode 100644 index 000000000..11c4dbbc7 --- /dev/null +++ b/BTCPayServer/Views/Shared/PayButton/NavExtension.cshtml @@ -0,0 +1,17 @@ +@using BTCPayServer.Client +@using BTCPayServer.Views.Stores +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.TagHelpers + +@{ var store = Context.GetStoreData(); } + +@if (store != null) +{ + +} + diff --git a/BTCPayServer/Views/UIStores/PayButton.cshtml b/BTCPayServer/Views/Shared/PayButton/PayButton.cshtml similarity index 96% rename from BTCPayServer/Views/UIStores/PayButton.cshtml rename to BTCPayServer/Views/Shared/PayButton/PayButton.cshtml index b4120d27a..ffe667586 100644 --- a/BTCPayServer/Views/UIStores/PayButton.cshtml +++ b/BTCPayServer/Views/Shared/PayButton/PayButton.cshtml @@ -1,5 +1,8 @@ -@inject BTCPayServer.Security.ContentSecurityPolicies csp -@model PayButtonViewModel +@inject Security.ContentSecurityPolicies csp +@using BTCPayServer.Views.Stores +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.TagHelpers +@model BTCPayServer.Plugins.PayButton.Models.PayButtonViewModel @{ ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id); csp.AllowUnsafeHashes("onBTCPayFormSubmit(event);return false"); @@ -137,9 +140,14 @@ -

Warning: This feature should not be activated on a BTCPay Server store processing commercial transactions.

-

By activating this feature, a malicious user can trick you into thinking an order has been processed by creating a new invoice, reusing the same Order Id of another valid order but different amount or currency.

-

If this store process commercial transactions, we advise you to create a separate store before using the payment button.

+
Warning: Payment button should only be used for tips and donations
+

+ Using the payment button for e-commerce integrations is not recommended since order relevant information can be modified by the user. + For e-commerce, you should use our + Greenfield API. + If this store process commercial transactions, we advise you to + create a separate store before using the payment button. +

diff --git a/BTCPayServer/Views/Shared/PayButton/_ViewImports.cshtml b/BTCPayServer/Views/Shared/PayButton/_ViewImports.cshtml new file mode 100644 index 000000000..5576b38b7 --- /dev/null +++ b/BTCPayServer/Views/Shared/PayButton/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.Plugins.PayButton.Views +@namespace BTCPayServer.Plugins.PayButton.Views +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/BTCPayServer/Views/UIStores/PayButtonEnable.cshtml b/BTCPayServer/Views/UIStores/PayButtonEnable.cshtml deleted file mode 100644 index f32a2cbd1..000000000 --- a/BTCPayServer/Views/UIStores/PayButtonEnable.cshtml +++ /dev/null @@ -1,29 +0,0 @@ -@{ - ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id); -} - - -

@ViewData["Title"]

- -
-
- -

- To start using Pay Button, you need to enable this feature explicitly. - Once you do so, anyone could create an invoice on your store (via API, for example). -

-
- @Html.Hidden("EnableStore", true) - -
-
-
diff --git a/BTCPayServer/wwwroot/paybutton/paybutton.js b/BTCPayServer/wwwroot/paybutton/paybutton.js index 098f64b49..b3ca94d97 100644 --- a/BTCPayServer/wwwroot/paybutton/paybutton.js +++ b/BTCPayServer/wwwroot/paybutton/paybutton.js @@ -153,11 +153,7 @@ function inputChanges(event, buttonSize) { html += ' \n'; } - if ( - allowDefaultPaymentMethodSelection && - // Only add default payment method to HTML if user explicitly selected it - event && event.target.id === 'default-payment-method' && event.target.value !== "" - ) + if (allowDefaultPaymentMethodSelection && srvModel.defaultPaymentMethod !== "") { html += addInput("defaultPaymentMethod", srvModel.defaultPaymentMethod) }