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.StoreViewModels;
using BTCPayServer.Models.WalletViewModels; using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
@ -617,6 +619,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("LTC"); user.RegisterDerivationScheme("LTC");
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString(); var appType = AppType.PointOfSale.ToString();
vm.AppName = "test"; vm.AppName = "test";
@ -624,8 +627,10 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0]; 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 };
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello"; vmpos.Title = "hello";
vmpos.Currency = "CAD"; vmpos.Currency = "CAD";
vmpos.ButtonText = "{0} Purchase"; vmpos.ButtonText = "{0} Purchase";
@ -642,8 +647,8 @@ donation:
price: 1.02 price: 1.02
custom: true custom: true
"; ";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result); Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.Equal("hello", vmpos.Title); Assert.Equal("hello", vmpos.Title);
var publicApps = user.GetController<UIAppsPublicController>(); var publicApps = user.GetController<UIAppsPublicController>();
@ -698,7 +703,7 @@ donation:
}) })
{ {
TestLogs.LogInformation($"Testing for {test.Code}"); 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.Title = "hello";
vmpos.Currency = test.Item1; vmpos.Currency = test.Item1;
vmpos.ButtonText = "{0} Purchase"; vmpos.ButtonText = "{0} Purchase";
@ -714,7 +719,7 @@ donation:
price: 1.02 price: 1.02
custom: true custom: true
"; ";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result); Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
publicApps = user.GetController<UIAppsPublicController>(); publicApps = user.GetController<UIAppsPublicController>();
vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>(); vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
Assert.Equal(test.Code, vmview.CurrencyCode); Assert.Equal(test.Code, vmview.CurrencyCode);
@ -731,7 +736,7 @@ donation:
} }
//test inventory related features //test inventory related features
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello"; vmpos.Title = "hello";
vmpos.Currency = "BTC"; vmpos.Currency = "BTC";
vmpos.Template = @" vmpos.Template = @"
@ -741,7 +746,7 @@ inventoryitem:
inventory: 1 inventory: 1
noninventoryitem: noninventoryitem:
price: 10.0"; 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 //inventoryitem has 1 item available
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() => await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
@ -777,13 +782,13 @@ noninventoryitem:
//check that item is back in stock //check that item is back in stock
await TestUtils.EventuallyAsync(async () => await TestUtils.EventuallyAsync(async () =>
{ {
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.Equal(1, Assert.Equal(1,
appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
}, 10000); }, 10000);
//test payment methods option //test payment methods option
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Title = "hello"; vmpos.Title = "hello";
vmpos.Currency = "BTC"; vmpos.Currency = "BTC";
vmpos.Template = @" vmpos.Template = @"
@ -794,7 +799,7 @@ btconly:
- BTC - BTC
normal: normal:
price: 1.0"; 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 Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result); .ViewPointOfSale(app.Id, PosViewType.Cart, 1, null, null, null, null, "btconly").Result);
Assert.IsType<RedirectToActionResult>(publicApps Assert.IsType<RedirectToActionResult>(publicApps
@ -838,8 +843,8 @@ g:
custom: topup custom: topup
"; ";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result); Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
Assert.DoesNotContain("custom", vmpos.Template); Assert.DoesNotContain("custom", vmpos.Template);
var items = appService.Parse(vmpos.Template, vmpos.Currency); var items = appService.Parse(vmpos.Template, vmpos.Currency);
Assert.Contains(items, item => item.Id == "a" && item.Price.Type == ViewPointOfSaleViewModel.Item.ItemPrice.ItemPriceType.Fixed); 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.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Controllers;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Tests.Logging; using BTCPayServer.Tests.Logging;
@ -35,6 +37,7 @@ namespace BTCPayServer.Tests
var stores = user.GetController<UIStoresController>(); var stores = user.GetController<UIStoresController>();
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var apps2 = user2.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 vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.Crowdfund.ToString(); var appType = AppType.Crowdfund.ToString();
Assert.NotNull(vm.SelectedAppType); Assert.NotNull(vm.SelectedAppType);
@ -42,10 +45,12 @@ namespace BTCPayServer.Tests
vm.AppName = "test"; vm.AppName = "test";
vm.SelectedAppType = appType; vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); 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 appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0]; 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 = var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model); Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
Assert.Single(appList.Apps); Assert.Single(appList.Apps);
@ -72,6 +77,7 @@ namespace BTCPayServer.Tests
await user.GrantAccessAsync(); await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var crowdfund = user.GetController<UICrowdfundController>();
var vm = apps.CreateApp(user.StoreId).AssertViewModel<CreateAppViewModel>(); var vm = apps.CreateApp(user.StoreId).AssertViewModel<CreateAppViewModel>();
var appType = AppType.Crowdfund.ToString(); var appType = AppType.Crowdfund.ToString();
vm.AppName = "test"; vm.AppName = "test";
@ -79,15 +85,17 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0]; 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 //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.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false; crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null; 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 anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>(); var publicApps = user.GetController<UIAppsPublicController>();
@ -112,7 +120,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.StartDate = DateTime.Today.AddDays(2); crowdfundViewModel.StartDate = DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true; 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() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
@ -123,7 +131,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1); crowdfundViewModel.EndDate = DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true; 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() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(0.01) Amount = new decimal(0.01)
@ -136,7 +144,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1; crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true; 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() Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(app.Id, new ContributeToCrowdfund()
{ {
Amount = new decimal(1.01) Amount = new decimal(1.01)
@ -160,6 +168,7 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Never); await user.SetNetworkFeeMode(NetworkFeeMode.Never);
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var crowdfund = user.GetController<UICrowdfundController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.Crowdfund.ToString(); var appType = AppType.Crowdfund.ToString();
vm.AppName = "test"; vm.AppName = "test";
@ -167,17 +176,19 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0]; 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"); 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.Enabled = true;
crowdfundViewModel.EndDate = null; crowdfundViewModel.EndDate = null;
crowdfundViewModel.TargetAmount = 100; crowdfundViewModel.TargetAmount = 100;
crowdfundViewModel.TargetCurrency = "BTC"; crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true; crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = 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 anonAppPubsController = tester.PayTester.GetController<UIAppsPublicController>();
var publicApps = user.GetController<UIAppsPublicController>(); var publicApps = user.GetController<UIAppsPublicController>();
@ -232,7 +243,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags); Assert.Contains(AppService.GetAppInternalTag(app.Id), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false; 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"); TestLogs.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice 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"); TestLogs.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false; crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true; 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 invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{ {
Buyer = new Buyer { email = "test@fwf.com" }, Buyer = new Buyer { email = "test@fwf.com" },

View file

@ -2,6 +2,8 @@ using System.Threading.Tasks;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Tests.Logging; using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -28,6 +30,7 @@ namespace BTCPayServer.Tests
await user.GrantAccessAsync(); await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("BTC");
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var pos = user.GetController<UIPointOfSaleController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model); var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString(); var appType = AppType.PointOfSale.ToString();
vm.AppName = "test"; vm.AppName = "test";
@ -35,8 +38,10 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model); var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0]; 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 };
var vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
var vmpos = await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
vmpos.Template = @" vmpos.Template = @"
apple: apple:
price: 5.0 price: 5.0
@ -48,8 +53,8 @@ donation:
price: 1.02 price: 1.02
custom: true custom: true
"; ";
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(app.Id, vmpos).Result); Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
vmpos = await apps.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>(); await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var publicApps = user.GetController<UIAppsPublicController>(); var publicApps = user.GetController<UIAppsPublicController>();
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>(); 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.Bitcoin;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payments.PayJoin.Sender; using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Security.Bitpay; using BTCPayServer.Security.Bitpay;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
@ -1953,6 +1954,7 @@ namespace BTCPayServer.Tests
var stores = user.GetController<UIStoresController>(); var stores = user.GetController<UIStoresController>();
var apps = user.GetController<UIAppsController>(); var apps = user.GetController<UIAppsController>();
var apps2 = user2.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 vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp(user.StoreId)).Model);
var appType = AppType.PointOfSale.ToString(); var appType = AppType.PointOfSale.ToString();
Assert.NotNull(vm.SelectedAppType); Assert.NotNull(vm.SelectedAppType);
@ -1960,12 +1962,14 @@ namespace BTCPayServer.Tests
vm.AppName = "test"; vm.AppName = "test";
vm.SelectedAppType = appType; vm.SelectedAppType = appType;
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(user.StoreId, vm).Result); 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 appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var appList2 = var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model); Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
var app = appList.Apps[0]; 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.Single(appList.Apps);
Assert.Empty(appList2.Apps); Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName); Assert.Equal("test", appList.Apps[0].AppName);

View file

@ -100,9 +100,7 @@
{ {
<li class="nav-item"> <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}")"> <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>@custodianAccount.Name</span>
<span class="badge bg-warning ms-1" style="font-size:10px;">Experimental</span> <span class="badge bg-warning ms-1" style="font-size:10px;">Experimental</span>
</a> </a>
@ -171,12 +169,7 @@
<ul class="navbar-nav"> <ul class="navbar-nav">
@foreach (var app in Model.Apps) @foreach (var app in Model.Apps)
{ {
<li class="nav-item"> <vc:ui-extension-point location="apps-nav" model="@app"/>
<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>
} }
<li class="nav-item"> <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"> <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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -71,12 +72,11 @@ namespace BTCPayServer.Components.MainNav
vm.Apps = apps.Select(a => new StoreApp vm.Apps = apps.Select(a => new StoreApp
{ {
Id = a.Id, Id = a.Id,
IsOwner = a.IsOwner,
AppName = a.AppName, AppName = a.AppName,
AppType = a.AppType, AppType = Enum.Parse<AppType>(a.AppType)
IsOwner = a.IsOwner
}).ToList(); }).ToList();
if (PoliciesSettings.Experimental) if (PoliciesSettings.Experimental)
{ {
// Custodian Accounts // Custodian Accounts

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,14 +7,36 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; 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.Apps;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; 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")] [HttpGet("{appId}/settings/pos")]
public async Task<IActionResult> UpdatePointOfSale(string appId) 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}"; 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")] [HttpPost("{appId}/settings/pos")]
@ -99,7 +121,7 @@ namespace BTCPayServer.Controllers
return NotFound(); return NotFound();
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(vm); return View("PointOfSale/UpdatePointOfSale", vm);
vm.Currency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.Currency); vm.Currency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.Currency);
if (_currencies.GetCurrencyData(vm.Currency, false) == null) if (_currencies.GetCurrencyData(vm.Currency, false) == null)
@ -114,7 +136,7 @@ namespace BTCPayServer.Controllers
} }
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return View(vm); return View("PointOfSale/UpdatePointOfSale", vm);
} }
app.Name = vm.AppName; app.Name = vm.AppName;
@ -157,5 +179,16 @@ namespace BTCPayServer.Controllers
return list.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); 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 BTCPayServer.Validation;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Models.AppViewModels namespace BTCPayServer.Plugins.PointOfSale.Models
{ {
public class UpdatePointOfSaleViewModel public class UpdatePointOfSaleViewModel
{ {

View file

@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
namespace BTCPayServer.Models.AppViewModels namespace BTCPayServer.Plugins.PointOfSale.Models
{ {
public class ViewPointOfSaleViewModel 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.Abstractions.Extensions;
using BTCPayServer.Controllers; using BTCPayServer.Controllers;
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Models;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View file

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

View file

@ -1,4 +1,5 @@
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.PointOfSale.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace BTCPayServer.Services.Invoices; 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 System.Globalization
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Models @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); 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.Services.Apps
@using BTCPayServer.Abstractions.Models @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); ViewData.SetActivePage(AppsNavPages.Update, "Update Point of Sale", Model.Id);
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,11 @@
@using BTCPayServer.Models.AppViewModels @using BTCPayServer.Models.AppViewModels
@using BTCPayServer.Payments.Lightning @using BTCPayServer.Payments.Lightning
@using BTCPayServer.Plugins.PointOfSale.Models
@using BTCPayServer.Services.Stores @using BTCPayServer.Services.Stores
@using LNURL @using LNURL
@inject BTCPayNetworkProvider BTCPayNetworkProvider @inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject StoreRepository StoreRepository @inject StoreRepository StoreRepository
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel @model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
<style> <style>
/* This hides unwanted metadata such as url, date, etc from appearing on a printed page. */ /* 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 @using BTCPayServer.Models.AppViewModels
@model BTCPayServer.Models.AppViewModels.ViewPointOfSaleViewModel @using BTCPayServer.Plugins.PointOfSale.Models
@model BTCPayServer.Plugins.PointOfSale.Models.ViewPointOfSaleViewModel
@{ @{
Layout = "_LayoutPos"; Layout = "_LayoutPos";
var anyInventoryItems = Model.Items.Any(item => item.Inventory.HasValue); 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> <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"> <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"> <div class="l-pos-header bg-primary py-3 px-3">

View file

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