mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-18 12:44:22 +01:00
Plugins can now build apps (#4608)
* Plugins can now build apps * fix tests * fixup * pluginize existing apps * Test fixes part 1 * Test fixes part 2 * Fix Crowdfund namespace * Syntax * More namespace fixes * Markup * Test fix * upstream fixes * Add plugin icon * Fix nullable build warnings * allow pre popualting app creation * Fixes after merge * Make link methods async * Use AppData as parameter for ConfigureLink * GetApps by AppType * Use ConfigureLink on dashboard * Rename method * Add properties to indicate stats support * Property updates * Test fixes * Clean up imports * Fixes after merge --------- Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
@@ -7,6 +7,8 @@ using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Plugins.Crowdfund;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
@@ -15,6 +17,7 @@ using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using PosViewType = BTCPayServer.Plugins.PointOfSale.PosViewType;
|
||||
|
||||
namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
@@ -63,7 +66,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Name = request.AppName,
|
||||
AppType = AppType.Crowdfund.ToString()
|
||||
AppType = CrowdfundApp.AppType
|
||||
};
|
||||
|
||||
appData.SetSettings(ToCrowdfundSettings(request));
|
||||
@@ -94,7 +97,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Name = request.AppName,
|
||||
AppType = AppType.PointOfSale.ToString()
|
||||
AppType = PointOfSaleApp.AppType
|
||||
};
|
||||
|
||||
appData.SetSettings(ToPointOfSaleSettings(request));
|
||||
@@ -108,7 +111,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> UpdatePointOfSaleApp(string appId, CreatePointOfSaleAppRequest request)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, AppType.PointOfSale);
|
||||
var app = await _appService.GetApp(appId, PointOfSaleApp.AppType);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -181,7 +184,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetPosApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, AppType.PointOfSale);
|
||||
var app = await _appService.GetApp(appId, PointOfSaleApp.AppType);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -194,7 +197,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetCrowdfundApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, AppType.Crowdfund);
|
||||
var app = await _appService.GetApp(appId, CrowdfundApp.AppType);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -264,7 +267,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new PointOfSaleSettings()
|
||||
{
|
||||
Title = request.Title,
|
||||
DefaultView = (Services.Apps.PosViewType)request.DefaultView,
|
||||
DefaultView = (PosViewType) request.DefaultView,
|
||||
ShowCustomAmount = request.ShowCustomAmount,
|
||||
ShowDiscount = request.ShowDiscount,
|
||||
EnableTips = request.EnableTips,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
@@ -6,8 +5,6 @@ using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -57,13 +54,14 @@ namespace BTCPayServer.Controllers
|
||||
var app = await _appService.GetApp(appId, null);
|
||||
if (app is null)
|
||||
return NotFound();
|
||||
|
||||
return app.AppType switch
|
||||
|
||||
var res = await _appService.ViewLink(app);
|
||||
if (res is null)
|
||||
{
|
||||
nameof(AppType.Crowdfund) => RedirectToAction(nameof(UICrowdfundController.ViewCrowdfund), "UICrowdfund", new { appId }),
|
||||
nameof(AppType.PointOfSale) => RedirectToAction(nameof(UIPointOfSaleController.ViewPointOfSale), "UIPointOfSale", new { appId }),
|
||||
_ => NotFound()
|
||||
};
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Redirect(res);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
@@ -114,12 +112,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpGet("/stores/{storeId}/apps/create")]
|
||||
public IActionResult CreateApp(string storeId)
|
||||
public IActionResult CreateApp(string storeId, string appType = null)
|
||||
{
|
||||
return View(new CreateAppViewModel
|
||||
{
|
||||
StoreId = GetCurrentStore().Id
|
||||
});
|
||||
var vm = new CreateAppViewModel (_appService){StoreId = GetCurrentStore().Id, SelectedAppType = appType};
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
@@ -128,8 +124,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var store = GetCurrentStore();
|
||||
vm.StoreId = store.Id;
|
||||
|
||||
if (!Enum.TryParse(vm.SelectedAppType, out AppType appType))
|
||||
var types = _appService.GetAvailableAppTypes();
|
||||
if (!types.ContainsKey(vm.SelectedAppType))
|
||||
ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type");
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
@@ -141,34 +137,18 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
StoreDataId = store.Id,
|
||||
Name = vm.AppName,
|
||||
AppType = appType.ToString()
|
||||
AppType = vm.SelectedAppType
|
||||
};
|
||||
|
||||
var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null);
|
||||
switch (appType)
|
||||
{
|
||||
case AppType.Crowdfund:
|
||||
var emptyCrowdfund = new CrowdfundSettings { TargetCurrency = defaultCurrency };
|
||||
appData.SetSettings(emptyCrowdfund);
|
||||
break;
|
||||
case AppType.PointOfSale:
|
||||
var empty = new PointOfSaleSettings { Currency = defaultCurrency };
|
||||
appData.SetSettings(empty);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
await _appService.SetDefaultSettings(appData, defaultCurrency);
|
||||
await _appService.UpdateOrCreateApp(appData);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
|
||||
CreatedAppId = appData.Id;
|
||||
|
||||
return appType switch
|
||||
{
|
||||
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()
|
||||
};
|
||||
var url = await _appService.ConfigureLink(appData, vm.SelectedAppType);
|
||||
return Redirect(url);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
|
||||
@@ -213,7 +213,8 @@ namespace BTCPayServer.Controllers
|
||||
return await CreateInvoiceCoreRaw(invoiceRequest, storeData, request.GetAbsoluteRoot(), additionalTags, cancellationToken);
|
||||
}
|
||||
|
||||
internal async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
||||
[NonAction]
|
||||
public async Task<InvoiceEntity> CreateInvoiceCoreRaw(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string>? additionalTags = null, CancellationToken cancellationToken = default, Action<InvoiceEntity>? entityManipulator = null)
|
||||
{
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var entity = _InvoiceRepository.CreateNewInvoice();
|
||||
|
||||
@@ -18,6 +18,8 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Plugins.Crowdfund;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@@ -47,7 +49,6 @@ namespace BTCPayServer
|
||||
private readonly LightningLikePaymentHandler _lightningLikePaymentHandler;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly LightningAddressService _lightningAddressService;
|
||||
@@ -155,6 +156,7 @@ namespace BTCPayServer
|
||||
|
||||
if (claimResponse.Result != ClaimRequest.ClaimResult.Ok)
|
||||
return BadRequest(new LNUrlStatusResponse { Status = "ERROR", Reason = "Payment request could not be paid" });
|
||||
|
||||
switch (claimResponse.PayoutData.State)
|
||||
{
|
||||
case PayoutState.AwaitingPayment:
|
||||
@@ -249,37 +251,49 @@ namespace BTCPayServer
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
ViewPointOfSaleViewModel.Item[] items = null;
|
||||
string currencyCode = null;
|
||||
ViewPointOfSaleViewModel.Item[] items;
|
||||
string currencyCode;
|
||||
PointOfSaleSettings posS = null;
|
||||
switch (app.AppType)
|
||||
{
|
||||
case nameof(AppType.Crowdfund):
|
||||
case CrowdfundApp.AppType:
|
||||
var cfS = app.GetSettings<CrowdfundSettings>();
|
||||
currencyCode = cfS.TargetCurrency;
|
||||
items = _appService.Parse(cfS.PerksTemplate, cfS.TargetCurrency);
|
||||
break;
|
||||
case nameof(AppType.PointOfSale):
|
||||
var posS = app.GetSettings<PointOfSaleSettings>();
|
||||
case PointOfSaleApp.AppType:
|
||||
posS = app.GetSettings<PointOfSaleSettings>();
|
||||
currencyCode = posS.Currency;
|
||||
items = _appService.Parse(posS.Template, posS.Currency);
|
||||
break;
|
||||
default:
|
||||
//TODO: Allow other apps to define lnurl support
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode);
|
||||
var item = items.FirstOrDefault(item1 =>
|
||||
item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
item1.Id.Equals(escapedItemId, StringComparison.InvariantCultureIgnoreCase));
|
||||
ViewPointOfSaleViewModel.Item item = null;
|
||||
if (!string.IsNullOrEmpty(itemCode))
|
||||
{
|
||||
var escapedItemId = Extensions.UnescapeBackSlashUriString(itemCode);
|
||||
item = items.FirstOrDefault(item1 =>
|
||||
item1.Id.Equals(itemCode, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
item1.Id.Equals(escapedItemId, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (item is null ||
|
||||
item.Inventory <= 0 ||
|
||||
(item.PaymentMethods?.Any() is true &&
|
||||
item.PaymentMethods?.Any(s => PaymentMethodId.Parse(s) == pmi) is false))
|
||||
if (item is null ||
|
||||
item.Inventory <= 0 ||
|
||||
(item.PaymentMethods?.Any() is true &&
|
||||
item.PaymentMethods?.Any(s => PaymentMethodId.Parse(s) == pmi) is false))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
else if (app.AppType == PointOfSaleApp.AppType && posS?.ShowCustomAmount is not true)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
return await GetLNURL(cryptoCode, app.StoreDataId, currencyCode, null, null,
|
||||
() => (null, app, item, new List<string> { AppService.GetAppInternalTag(appId) }, item.Price.Value, true));
|
||||
() => (null, app, item, new List<string> { AppService.GetAppInternalTag(appId) }, item?.Price.Value, true));
|
||||
}
|
||||
|
||||
public class EditLightningAddressVM
|
||||
@@ -311,11 +325,8 @@ namespace BTCPayServer
|
||||
public decimal? Max { get; set; }
|
||||
}
|
||||
|
||||
public ConcurrentDictionary<string, LightningAddressItem> Items { get; set; } =
|
||||
new ConcurrentDictionary<string, LightningAddressItem>();
|
||||
|
||||
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; set; } =
|
||||
new ConcurrentDictionary<string, string[]>();
|
||||
public ConcurrentDictionary<string, LightningAddressItem> Items { get; } = new ();
|
||||
public ConcurrentDictionary<string, string[]> StoreToItemMap { get; } = new ();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -389,7 +400,7 @@ namespace BTCPayServer
|
||||
|
||||
var redirectUrl = app?.AppType switch
|
||||
{
|
||||
nameof(AppType.PointOfSale) => app.GetSettings<PointOfSaleSettings>().RedirectUrl ??
|
||||
PointOfSaleApp.AppType => app.GetSettings<PointOfSaleSettings>().RedirectUrl ??
|
||||
HttpContext.Request.GetAbsoluteUri($"/apps/{app.Id}/pos"),
|
||||
_ => null
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Web;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Plugins.PayButton.Models;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
@@ -347,7 +347,7 @@ namespace BTCPayServer.Controllers
|
||||
if (appIdsToFetch.Any())
|
||||
{
|
||||
var apps = (await _AppService.GetApps(appIdsToFetch.ToArray()))
|
||||
.ToDictionary(data => data.Id, data => Enum.Parse<AppType>(data.AppType));
|
||||
.ToDictionary(data => data.Id, data => data.AppType);
|
||||
;
|
||||
if (!string.IsNullOrEmpty(settings.RootAppId))
|
||||
{
|
||||
@@ -422,8 +422,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task<List<SelectListItem>> GetAppSelectList()
|
||||
{
|
||||
var types = _AppService.GetAvailableAppTypes();
|
||||
var apps = (await _AppService.GetAllApps(null, true))
|
||||
.Select(a => new SelectListItem($"{typeof(AppType).DisplayName(a.AppType)} - {a.AppName} - {a.StoreName}", a.Id)).ToList();
|
||||
.Select(a =>
|
||||
new SelectListItem($"{types[a.AppType]} - {a.AppName} - {a.StoreName}", a.Id)).ToList();
|
||||
apps.Insert(0, new SelectListItem("(None)", null));
|
||||
return apps;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user