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 <pavle@pavle.org>

* Update pay button warning

Closes #3535.

Co-authored-by: Pavlenex <pavle@pavle.org>
This commit is contained in:
d11n 2022-06-15 04:32:46 +02:00 committed by GitHub
parent 8a144f3c35
commit f0e013e1f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 199 additions and 118 deletions

View file

@ -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()
{

View file

@ -182,12 +182,6 @@
<Content Update="Views\UIStores\ShowToken.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\UIStores\PayButtonEnable.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\UIStores\PayButton.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>
<Content Update="Views\UIPublic\PayButtonHandle.cshtml">
<Pack>$(IncludeRazorContentInPack)</Pack>
</Content>

View file

@ -129,12 +129,6 @@
<span>Payouts</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIStores" asp-action="PayButton" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" id="StoreNav-PayButton">
<vc:icon symbol="pay-button"/>
<span>Pay Button</span>
</a>
</li>
</ul>
</div>
</div>

View file

@ -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;

View file

@ -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<ExternalServicesOptions> 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<ExternalServicesOptions> _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<IActionResult> 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<IActionResult> 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<IActionResult> 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
});
}
}
}

View file

@ -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<ApplicationUser> userManager,
AppService appService)
{
_repo = repo;
_userManager = userManager;
_appService = appService;
_storesController = storesController;
}
readonly StoreRepository _repo;
readonly UserManager<ApplicationUser> _userManager;
private readonly AppService _appService;
private readonly UIStoresController _storesController;
[HttpPost("{storeId}/disable-anyone-can-pay")]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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();
}
}

View file

@ -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
{

View file

@ -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<IUIExtension>(new UIExtension("PayButton/NavExtension", "header-nav"));
base.Execute(services);
}
}
}

View file

@ -0,0 +1,36 @@
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
}
<partial name="_StatusMessage" />
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<div class="alert alert-warning alert-dismissible mb-4" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
<vc:icon symbol="close" />
</button>
<h5 class="alert-heading">Warning: Payment button should only be used for tips and donations</h5>
<p>
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
<a href="https://docs.btcpayserver.org/API/Greenfield/v1/" class="alert-link" target="_blank" rel="noreferrer noopener">Greenfield API</a>.
If this store process commercial transactions, we advise you to
<a asp-controller="UIUserStores" asp-action="CreateStore" class="alert-link">create a separate store</a> before using the payment button.
</p>
</div>
<p>
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).
</p>
<form method="post">
@Html.Hidden("EnableStore", true)
<button name="command" id="enable-pay-button" type="submit" value="save" class="btn btn-primary">
Enable
</button>
</form>
</div>
</div>

View file

@ -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)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIPayButton" asp-action="PayButton" asp-route-storeId="@store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" id="StoreNav-PayButton">
<vc:icon symbol="pay-button"/>
<span>Pay Button</span>
</a>
</li>
}

View file

@ -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 @@
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
<vc:icon symbol="close" />
</button>
<p><strong>Warning:</strong> This feature should not be activated on a BTCPay Server store processing commercial transactions.</p>
<p>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.</p>
<p>If this store process commercial transactions, we advise you to <a asp-controller="UIUserStores" asp-action="CreateStore" class="alert-link">create a separate store</a> before using the payment button.</p>
<h5 class="alert-heading">Warning: Payment button should only be used for tips and donations</h5>
<p>
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
<a href="https://docs.btcpayserver.org/API/Greenfield/v1/" class="alert-link" target="_blank" rel="noreferrer noopener">Greenfield API</a>.
If this store process commercial transactions, we advise you to
<a asp-controller="UIUserStores" asp-action="CreateStore" class="alert-link">create a separate store</a> before using the payment button.
</p>
<form asp-action="DisableAnyoneCanCreateInvoice" asp-route-storeId="@Context.GetRouteValue("storeId")" method="post">
<button name="command" id="disable-pay-button" type="submit" class="btn btn-danger mt-0" value="Save">Disable payment button</button>
</form>

View file

@ -0,0 +1,4 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Plugins.PayButton.Views
@namespace BTCPayServer.Plugins.PayButton.Views
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -1,29 +0,0 @@
@{
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
}
<partial name="_StatusMessage" />
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div class="row">
<div class="col-md-10">
<div class="alert alert-warning alert-dismissible mb-4" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
<vc:icon symbol="close" />
</button>
<p><strong>Warning:</strong> This feature should not be activated on a BTCPay Server store processing commercial transactions.</p>
<p>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.</p>
<p class="mb-0">If this store process commercial transactions, we advise you to <a asp-controller="UIUserStores" asp-action="CreateStore" class="alert-link">create a separate store</a> before using the payment button.</p>
</div>
<p>
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).
</p>
<form method="post">
@Html.Hidden("EnableStore", true)
<button name="command" id="enable-pay-button" type="submit" value="save" class="btn btn-primary">
Enable
</button>
</form>
</div>
</div>

View file

@ -153,11 +153,7 @@ function inputChanges(event, buttonSize) {
html += ' </div>\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)
}