#nullable enable using System; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Stores; using BTCPayServer.Abstractions.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers.Greenfield { [ApiController] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [EnableCors(CorsPolicies.All)] public class GreenfieldAppsController : ControllerBase { private readonly AppService _appService; private readonly StoreRepository _storeRepository; public GreenfieldAppsController( AppService appService, StoreRepository storeRepository, UserManager userManager, BTCPayNetworkProvider btcPayNetworkProvider ) { _appService = appService; _storeRepository = storeRepository; } [HttpPost("~/api/v1/stores/{storeId}/apps/pos")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task CreatePointOfSaleApp(string storeId, CreatePointOfSaleAppRequest request) { var validationResult = Validate(request); if (validationResult != null) { return validationResult; } var store = await _storeRepository.FindStore(storeId); if (store == null) return this.CreateAPIError(404, "store-not-found", "The store was not found"); var defaultCurrency = store.GetStoreBlob().DefaultCurrency; var appData = new AppData { StoreDataId = storeId, Name = request.AppName, AppType = AppType.PointOfSale.ToString() }; appData.SetSettings(new PointOfSaleSettings { Title = request.Title, DefaultView = (Services.Apps.PosViewType)request.DefaultView, ShowCustomAmount = request.ShowCustomAmount, ShowDiscount = request.ShowDiscount, EnableTips = request.EnableTips, Currency = request.Currency ?? defaultCurrency, Template = request.Template, ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF, CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF, CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF, CustomCSSLink = request.CustomCSSLink, NotificationUrl = request.NotificationUrl, RedirectUrl = request.RedirectUrl, Description = request.Description, EmbeddedCSS = request.EmbeddedCSS, RedirectAutomatically = request.RedirectAutomatically, RequiresRefundEmail = request.RequiresRefundEmail == true ? RequiresRefundEmail.On : request.RequiresRefundEmail == false ? RequiresRefundEmail.Off : RequiresRefundEmail.InheritFromStore, }); await _appService.UpdateOrCreateApp(appData); return Ok(ToModel(appData)); } [HttpGet("~/api/v1/apps/{appId}")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] public async Task GetApp(string appId) { var app = await _appService.GetApp(appId, AppType.PointOfSale); if (app == null) { return AppNotFound(); } return Ok(ToModel(app)); } [HttpDelete("~/api/v1/apps/{appId}")] public async Task DeleteApp(string appId) { var app = await _appService.GetApp(appId, null); if (app == null) { return AppNotFound(); } await _appService.DeleteApp(app); return Ok(); } private IActionResult AppNotFound() { return this.CreateAPIError(404, "app-not-found", "The app with specified ID was not found"); } private PointOfSaleAppData ToModel(AppData appData) { return new PointOfSaleAppData { Id = appData.Id, AppType = appData.AppType, Name = appData.Name, StoreId = appData.StoreDataId, Created = appData.Created }; } private IActionResult? Validate(CreateAppRequest request) { if (request is null) { return BadRequest(); } if (string.IsNullOrEmpty(request.AppName)) { ModelState.AddModelError(nameof(request.AppName), "App name is missing"); } else if (request.AppName.Length < 1 || request.AppName.Length > 50) { ModelState.AddModelError(nameof(request.AppName), "Name can only be between 1 and 50 characters"); } return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null; } } }