Make POS and Crowdfund plugins

This commit is contained in:
Dennis Reimann 2022-07-18 20:51:53 +02:00 committed by Andrew Camilleri
parent e4542c4ac4
commit 8c6705bccb
35 changed files with 273 additions and 95 deletions

View file

@ -11,6 +11,8 @@ using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
@ -617,6 +619,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("LTC");
var apps = user.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString();
vm.AppName = "test";
@ -624,8 +627,10 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "CAD";
vmpos.ButtonText = "{0} Purchase";
@ -642,8 +647,8 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.Equal("hello", vmpos.Title);
var publicApps = user.GetController<UIAppsPublicController>();
@ -698,7 +703,7 @@ donation:
})
{
TestLogs.LogInformation($"Testing for {test.Code}");
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = test.Item1;
vmpos.ButtonText = "{0} Purchase";
@ -714,7 +719,7 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
publicApps = user.GetController<UIAppsPublicController>();
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
Assert.Equal(test.Code, vmview.CurrencyCode);
@ -731,7 +736,7 @@ donation:
}
//test inventory related features
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "BTC";
vmpos.Template = @"
@ -741,7 +746,7 @@ inventoryitem:
inventory: 1
noninventoryitem:
price: 10.0";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
//inventoryitem has 1 item available
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
@ -777,13 +782,13 @@ noninventoryitem:
//check that item is back in stock
await TestUtils.EventuallyAsync(async () =>
{
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.Equal(1,
appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
}, 10000);
//test payment methods option
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello";
vmpos.Currency = "BTC";
vmpos.Template = @"
@ -794,7 +799,7 @@ btconly:
- BTC
normal:
price: 1.0";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result);
Assert.IsType<RedirectToActionResult>(publicApps
@ -838,8 +843,8 @@ g:
custom: topup
";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.DoesNotContain("custom", vmpos.Template);
var items = appService.Parse(vmpos.Template, vmpos.Currency);
Assert.Contains(items, item => item.Id == "a" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed);

View file

@ -4,6 +4,8 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Controllers;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Tests.Logging;
@ -35,6 +37,7 @@ namespace BTCPayServer.Tests
var stores = user.GetController<UIStoresController>();
var apps = user.GetController<UIAppsController>();
var apps2 = user2.GetController<UIAppsController>();
var crowdfund = user.GetController<UICrowdfundController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.Crowdfund.ToString();
Assert.NotNull(vm.SelectedAppType);
@ -42,10 +45,12 @@ namespace BTCPayServer.Tests
vm.AppName = "test";
vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
Assert.Equal(nameof(crowdfund.UpdateCrowdfund), redirectToAction.ActionName);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType});
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
Assert.Single(appList.Apps);
@ -72,6 +77,7 @@ namespace BTCPayServer.Tests
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>();
var crowdfund = user.GetController<UICrowdfundController>();
var vm = apps.CreateApp(user.StoreId).AssertViewModel<CreateAppViewModel>();
var appType = AppType.Crowdfund.ToString();
vm.AppName = "test";
@ -79,15 +85,17 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
//Scenario 1: Not Enabled - Not Allowed
var crowdfundViewModel = await apps.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
@ -112,7 +120,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -123,7 +131,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
@ -136,7 +144,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
@ -160,6 +168,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Never);
var apps = user.GetController<UIAppsController>();
var crowdfund = user.GetController<UICrowdfundController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.Crowdfund.ToString();
vm.AppName = "test";
@ -167,17 +176,19 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);
TestLogs.LogInformation("We create an invoice with a hardcap");
var crowdfundViewModel = await apps.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.Enabled = true;
crowdfundViewModel.EndDate = null;
crowdfundViewModel.TargetAmount = 100;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>();
@ -232,7 +243,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
@ -251,7 +262,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Buyer = new Buyer { email = "test@fwf.com" },

View file

@ -2,6 +2,8 @@ using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
@ -28,6 +30,7 @@ namespace BTCPayServer.Tests
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString();
vm.AppName = "test";
@ -35,8 +38,10 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Template = @"
apple:
price: 5.0
@ -48,8 +53,8 @@ donation:
price: 1.02
custom: true
";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var publicApps = user.GetController<UIAppsPublicController>();
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();

View file

@ -35,6 +35,7 @@ using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Security.Bitpay;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
@ -1953,6 +1954,7 @@ namespace BTCPayServer.Tests
var stores = user.GetController<UIStoresController>();
var apps = user.GetController<UIAppsController>();
var apps2 = user2.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString();
Assert.NotNull(vm.SelectedAppType);
@ -1960,12 +1962,14 @@ namespace BTCPayServer.Tests
vm.AppName = "test";
vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName);
Assert.Equal(nameof(pos.UpdatePointOfSale), redirectToAction.ActionName);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType });
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType };
apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
Assert.Single(appList.Apps);
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);

View file

@ -100,9 +100,7 @@
{
<li class="nav-item">
<a asp-area="" asp-controller="UICustodianAccounts" asp-action="ViewCustodianAccount" asp-route-storeId="@custodianAccount.StoreId" asp-route-accountId="@custodianAccount.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(CustodianAccountsNavPages.View, custodianAccount.Id)" id="@($"StoreNav-CustodianAccount-{custodianAccount.Id}")">
<!--
TODO which icon should we use?
-->
@* TODO which icon should we use? *@
<span>@custodianAccount.Name</span>
<span class="badge bg-warning ms-1" style="font-size:10px;">Experimental</span>
</a>
@ -171,12 +169,7 @@
<ul class="navbar-nav">
@foreach (var app in Model.Apps)
{
<li class="nav-item">
<a asp-area="" asp-controller="UIApps" asp-action="@app.Action" asp-route-appId="@app.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<vc:icon symbol="@app.AppType.ToLower()"/>
<span>@app.AppName</span>
</a>
</li>
<vc:ui-extension-point location="apps-nav" model="@app"/>
}
<li class="nav-item">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Create)" id="StoreNav-CreateApp">

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -71,12 +72,11 @@ namespace BTCPayServer.Components.MainNav
vm.Apps = apps.Select(a => new StoreApp
{
Id = a.Id,
IsOwner = a.IsOwner,
AppName = a.AppName,
AppType = a.AppType,
IsOwner = a.IsOwner
AppType = Enum.Parse<AppType>(a.AppType)
}).ToList();
if (PoliciesSettings.Experimental)
{
// Custodian Accounts

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Services.Apps;
namespace BTCPayServer.Components.MainNav
{
@ -18,8 +19,7 @@ namespace BTCPayServer.Components.MainNav
{
public string Id { get; set; }
public string AppName { get; set; }
public string AppType { get; set; }
public string Action { get => $"Update{AppType}"; }
public AppType AppType { get; set; }
public bool IsOwner { get; set; }
}
}

View file

@ -5,10 +5,10 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Controllers;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@ -23,25 +23,31 @@ namespace BTCPayServer.Controllers
{
public UIAppsController(
UserManager<ApplicationUser> userManager,
EventAggregator eventAggregator,
CurrencyNameTable currencies,
StoreRepository storeRepository,
AppService appService)
{
_userManager = userManager;
_eventAggregator = eventAggregator;
_currencies = currencies;
_storeRepository = storeRepository;
_appService = appService;
}
private readonly UserManager<ApplicationUser> _userManager;
private readonly EventAggregator _eventAggregator;
private readonly CurrencyNameTable _currencies;
private readonly StoreRepository _storeRepository;
private readonly AppService _appService;
public string CreatedAppId { get; set; }
public class AppUpdated
{
public string AppId { get; set; }
public object Settings { get; set; }
public string StoreId { get; set; }
public override string ToString()
{
return string.Empty;
}
}
[HttpGet("/stores/{storeId}/apps")]
public async Task<IActionResult> ListApps(
@ -139,8 +145,8 @@ namespace BTCPayServer.Controllers
return appType switch
{
AppType.PointOfSale => RedirectToAction(nameof(UpdatePointOfSale), new { appId = appData.Id }),
AppType.Crowdfund => RedirectToAction(nameof(UpdateCrowdfund), new { appId = appData.Id }),
AppType.PointOfSale => RedirectToAction(nameof(UIPointOfSaleController.UpdatePointOfSale), "UIPointOfSale", new { appId = appData.Id }),
AppType.Crowdfund => RedirectToAction(nameof(UICrowdfundController.UpdateCrowdfund), "UICrowdfund", new { appId = appData.Id }),
_ => throw new ArgumentOutOfRangeException()
};
}

View file

@ -12,6 +12,8 @@ using BTCPayServer.Filters;
using BTCPayServer.ModelBinders;
using BTCPayServer.Models;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http.Extensions;

View file

@ -19,6 +19,7 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;

View file

@ -15,6 +15,7 @@ using BTCPayServer.Logging;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Stores;

View file

@ -3,26 +3,39 @@ using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
namespace BTCPayServer.Plugins.Crowdfund.Controllers
{
public partial class UIAppsController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
[Route("apps")]
public class UICrowdfundController : Controller
{
public class AppUpdated
public UICrowdfundController(
EventAggregator eventAggregator,
CurrencyNameTable currencies,
StoreRepository storeRepository,
AppService appService)
{
public string AppId { get; set; }
public object Settings { get; set; }
public string StoreId { get; set; }
public override string ToString()
{
return string.Empty;
}
_eventAggregator = eventAggregator;
_currencies = currencies;
_storeRepository = storeRepository;
_appService = appService;
}
private readonly EventAggregator _eventAggregator;
private readonly CurrencyNameTable _currencies;
private readonly StoreRepository _storeRepository;
private readonly AppService _appService;
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("{appId}/settings/crowdfund")]
public async Task<IActionResult> UpdateCrowdfund(string appId)
@ -69,7 +82,7 @@ namespace BTCPayServer.Controllers
Sounds = string.Join(Environment.NewLine, settings.Sounds),
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
};
return View(vm);
return View("Crowdfund/UpdateCrowdfund", vm);
}
[HttpPost("{appId}/settings/crowdfund")]
@ -138,7 +151,7 @@ namespace BTCPayServer.Controllers
if (!ModelState.IsValid)
{
return View(vm);
return View("Crowdfund/UpdateCrowdfund", vm);
}
app.Name = vm.AppName;
@ -176,7 +189,7 @@ namespace BTCPayServer.Controllers
await _appService.UpdateOrCreateApp(app);
_eventAggregator.Publish(new AppUpdated()
_eventAggregator.Publish(new UIAppsController.AppUpdated
{
AppId = appId,
StoreId = app.StoreDataId,
@ -185,5 +198,16 @@ namespace BTCPayServer.Controllers
TempData[WellKnownTempData.SuccessMessage] = "App updated";
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
}
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
{
if (string.IsNullOrWhiteSpace(currency))
{
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
}
return currency.Trim().ToUpperInvariant();
}
private AppData GetCurrentApp() => HttpContext.GetAppData();
}
}

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 CrowdfundPlugin : BaseBTCPayServerPlugin
{
public override string Identifier => "BTCPayServer.Plugins.Crowdfund";
public override string Name => "Crowdfund";
public override string Description => "Create a self-hosted funding campaign, similar to Kickstarter or Indiegogo. Funds go directly to the creators wallet without any fees.";
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IUIExtension>(new UIExtension("Crowdfund/NavExtension", "apps-nav"));
base.Execute(services);
}
}
}

View file

@ -5,7 +5,7 @@ using System.Linq;
using BTCPayServer.Services.Apps;
using BTCPayServer.Validation;
namespace BTCPayServer.Models.AppViewModels
namespace BTCPayServer.Plugins.Crowdfund.Models
{
public class UpdateCrowdfundViewModel
{

View file

@ -2,10 +2,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Rates;
namespace BTCPayServer.Models.AppViewModels
namespace BTCPayServer.Plugins.Crowdfund.Models
{
public class ViewCrowdfundViewModel
{

View file

@ -7,14 +7,36 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
namespace BTCPayServer.Plugins.PointOfSale.Controllers
{
public partial class UIAppsController
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
[Route("apps")]
public class UIPointOfSaleController : Controller
{
public UIPointOfSaleController(
CurrencyNameTable currencies,
StoreRepository storeRepository,
AppService appService)
{
_currencies = currencies;
_storeRepository = storeRepository;
_appService = appService;
}
private readonly CurrencyNameTable _currencies;
private readonly StoreRepository _storeRepository;
private readonly AppService _appService;
[HttpGet("{appId}/settings/pos")]
public async Task<IActionResult> UpdatePointOfSale(string appId)
{
@ -88,7 +110,7 @@ namespace BTCPayServer.Controllers
}
vm.ExampleCallback = "{\n \"id\":\"SkdsDghkdP3D3qkj7bLq3\",\n \"url\":\"https://btcpay.example.com/invoice?id=SkdsDghkdP3D3qkj7bLq3\",\n \"status\":\"paid\",\n \"price\":10,\n \"currency\":\"EUR\",\n \"invoiceTime\":1520373130312,\n \"expirationTime\":1520374030312,\n \"currentTime\":1520373179327,\n \"exceptionStatus\":false,\n \"buyerFields\":{\n \"buyerEmail\":\"customer@example.com\",\n \"buyerNotify\":false\n },\n \"paymentSubtotals\": {\n \"BTC\":114700\n },\n \"paymentTotals\": {\n \"BTC\":118400\n },\n \"transactionCurrency\": \"BTC\",\n \"amountPaid\": \"1025900\",\n \"exchangeRates\": {\n \"BTC\": {\n \"EUR\": 8721.690715789999,\n \"USD\": 10817.99\n }\n }\n}";
return View(vm);
return View("PointOfSale/UpdatePointOfSale", vm);
}
[HttpPost("{appId}/settings/pos")]
@ -99,7 +121,7 @@ namespace BTCPayServer.Controllers
return NotFound();
if (!ModelState.IsValid)
return View(vm);
return View("PointOfSale/UpdatePointOfSale", vm);
vm.Currency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.Currency);
if (_currencies.GetCurrencyData(vm.Currency, false) == null)
@ -114,7 +136,7 @@ namespace BTCPayServer.Controllers
}
if (!ModelState.IsValid)
{
return View(vm);
return View("PointOfSale/UpdatePointOfSale", vm);
}
app.Name = vm.AppName;
@ -157,5 +179,16 @@ namespace BTCPayServer.Controllers
return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
{
if (string.IsNullOrWhiteSpace(currency))
{
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
}
return currency.Trim().ToUpperInvariant();
}
private AppData GetCurrentApp() => HttpContext.GetAppData();
}
}

View file

@ -4,7 +4,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Validation;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Models.AppViewModels
namespace BTCPayServer.Plugins.PointOfSale.Models
{
public class UpdatePointOfSaleViewModel
{

View file

@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServer.Services.Apps;
namespace BTCPayServer.Models.AppViewModels
namespace BTCPayServer.Plugins.PointOfSale.Models
{
public class ViewPointOfSaleViewModel
{

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 PointOfSalePlugin : BaseBTCPayServerPlugin
{
public override string Identifier => "BTCPayServer.Plugins.PointOfSale";
public override string Name => "Point Of Sale";
public override string Description => "Readily accept bitcoin without fees or a third-party, directly to your wallet.";
public override void Execute(IServiceCollection services)
{
services.AddSingleton<IUIExtension>(new UIExtension("PointOfSale/NavExtension", "apps-nav"));
base.Execute(services);
}
}
}

View file

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Controllers;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

View file

@ -8,6 +8,8 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
@ -21,7 +23,7 @@ using Newtonsoft.Json.Linq;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using static BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel;
using static BTCPayServer.Plugins.Crowdfund.Models.ViewCrowdfundViewModel;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Services.Apps

View file

@ -1,4 +1,5 @@
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.PointOfSale.Models;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Invoices;

View file

@ -0,0 +1,19 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Services.Apps
@model BTCPayServer.Components.MainNav.StoreApp
@{ var store = Context.GetStoreData(); }
@if (store != null && Model.AppType == AppType.Crowdfund)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@Model.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Update, Model.Id)" id="@($"StoreNav-App-{Model.Id}")">
<vc:icon symbol="@Model.AppType.ToString().ToLower()"/>
<span>@Model.AppName</span>
</a>
</li>
}

View file

@ -1,6 +1,10 @@
@using System.Globalization
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Models
@model UpdateCrowdfundViewModel
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Crowdfund", Model.AppId);
}

View file

@ -0,0 +1,20 @@
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Services.Apps
@model BTCPayServer.Components.MainNav.StoreApp
@{ var store = Context.GetStoreData(); }
@if (store != null && Model.AppType == AppType.PointOfSale)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@Model.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(AppsNavPages.Update, Model.Id)" id="@($"StoreNav-App-{Model.Id}")">
<vc:icon symbol="@Model.AppType.ToString().ToLower()"/>
<span>@Model.AppName</span>
</a>
</li>
}

View file

@ -1,6 +1,8 @@
@using BTCPayServer.Services.Apps
@using BTCPayServer.Abstractions.Models
@model UpdatePointOfSaleViewModel
@using BTCPayServer.Views.Apps
@using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Plugins.PointOfSale.Models.UpdatePointOfSaleViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, "Update Point of Sale", Model.Id);
}

View file

@ -2,7 +2,7 @@
@using BTCPayServer.Services.Apps
@using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{
ViewData["Title"] = Model.Title;

View file

@ -1,5 +1,6 @@
@using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Models.AppViewModels.ContributeToCrowdfund
@using BTCPayServer.Plugins.PointOfSale.Models
@model BTCPayServer.Plugins.Crowdfund.Models.ContributeToCrowdfund
@{ var vm = Model.ViewCrowdfundViewModel; }

View file

@ -1,5 +1,6 @@
@using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@using BTCPayServer.Plugins.PointOfSale.Models
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{
Layout = "_LayoutPos";
int[] customTipPercentages = Model.CustomTipPercentages;

View file

@ -1,4 +1,4 @@
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{
Layout = "_LayoutPos";
}

View file

@ -1,10 +1,11 @@
@using BTCPayServer.Models.AppViewModels
@using BTCPayServer.Payments.Lightning
@using BTCPayServer.Plugins.PointOfSale.Models
@using BTCPayServer.Services.Stores
@using LNURL
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject StoreRepository StoreRepository
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
<style>
/* This hides unwanted metadata such as url, date, etc from appearing on a printed page. */

View file

@ -1,5 +1,6 @@
@using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@using BTCPayServer.Plugins.PointOfSale.Models
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{
Layout = "_LayoutPos";
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue);

View file

@ -1,4 +1,4 @@
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
<div id="app" class="l-pos-wrapper" v-cloak>
<form method="post" asp-controller="UIAppsPublic" asp-action="ViewPointOfSale" asp-route-appId="@Model.AppId" asp-antiforgery="false" data-buy v-on:submit="handleFormSubmit">
<div class="l-pos-header bg-primary py-3 px-3">

View file

@ -1,7 +1,5 @@
@model BTCPayServer.Models.AppViewModels.ViewCrowdfundViewModel
@inject ISettingsRepository SettingsRepository
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Plugins.Crowdfund.Models.ViewCrowdfundViewModel
@using BTCPayServer.Plugins.Crowdfund.Models
@inject BTCPayServer.Services.ThemeSettings Theme
@{
ViewData["Title"] = Model.Title;