Branding updates for 2.0 (#5947)

* Remove deprecated CSS options

Closes #5945.

* Greenfield: Add brandColor to store APIs

Closes #5946.

* Migrate file IDs to URLs

Closes #5953.

* Greenfield: Add CSS and logo URL to store settings API

Closes #5945.

* Add migration test

* Store and Server branding can reference file's via fileid:ID

* Add PaymentSoundUrl to Store API

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
d11n
2024-05-09 02:18:02 +02:00
committed by GitHub
parent eba3475a1b
commit 4c303d358b
66 changed files with 526 additions and 605 deletions

View File

@@ -35,13 +35,11 @@ namespace BTCPayServer.Client.Models
public string CustomAmountPayButtonText { get; set; } = null; public string CustomAmountPayButtonText { get; set; } = null;
public string FixedAmountPayButtonText { get; set; } = null; public string FixedAmountPayButtonText { get; set; } = null;
public string TipText { get; set; } = null; public string TipText { get; set; } = null;
public string CustomCSSLink { get; set; } = null;
public string NotificationUrl { get; set; } = null; public string NotificationUrl { get; set; } = null;
public string RedirectUrl { get; set; } = null; public string RedirectUrl { get; set; } = null;
public bool? RedirectAutomatically { get; set; } = null; public bool? RedirectAutomatically { get; set; } = null;
public bool? Archived { get; set; } = null; public bool? Archived { get; set; } = null;
public string FormId { get; set; } = null; public string FormId { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
} }
public enum CrowdfundResetEvery public enum CrowdfundResetEvery
@@ -65,9 +63,7 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))] [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? EndDate { get; set; } = null; public DateTimeOffset? EndDate { get; set; } = null;
public decimal? TargetAmount { get; set; } = null; public decimal? TargetAmount { get; set; } = null;
public string CustomCSSLink { get; set; } = null;
public string MainImageUrl { get; set; } = null; public string MainImageUrl { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
public string NotificationUrl { get; set; } = null; public string NotificationUrl { get; set; } = null;
public string Tagline { get; set; } = null; public string Tagline { get; set; } = null;
public string PerksTemplate { get; set; } = null; public string PerksTemplate { get; set; } = null;

View File

@@ -17,9 +17,6 @@ namespace BTCPayServer.Client.Models
public string Title { get; set; } public string Title { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string EmbeddedCSS { get; set; }
public string CustomCSSLink { get; set; }
public bool AllowCustomPaymentAmounts { get; set; } public bool AllowCustomPaymentAmounts { get; set; }
[JsonExtensionData] [JsonExtensionData]

View File

@@ -30,11 +30,9 @@ namespace BTCPayServer.Client.Models
public string FixedAmountPayButtonText { get; set; } public string FixedAmountPayButtonText { get; set; }
public string CustomAmountPayButtonText { get; set; } public string CustomAmountPayButtonText { get; set; }
public string TipText { get; set; } public string TipText { get; set; }
public string CustomCSSLink { get; set; }
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }
public string RedirectUrl { get; set; } public string RedirectUrl { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string EmbeddedCSS { get; set; }
public bool? RedirectAutomatically { get; set; } public bool? RedirectAutomatically { get; set; }
} }
@@ -50,9 +48,7 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))] [JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? EndDate { get; set; } public DateTimeOffset? EndDate { get; set; }
public decimal? TargetAmount { get; set; } public decimal? TargetAmount { get; set; }
public string CustomCSSLink { get; set; }
public string MainImageUrl { get; set; } public string MainImageUrl { get; set; }
public string EmbeddedCSS { get; set; }
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }
public string Tagline { get; set; } public string Tagline { get; set; }
public object Perks { get; set; } public object Perks { get; set; }

View File

@@ -16,6 +16,11 @@ namespace BTCPayServer.Client.Models
public string Website { get; set; } public string Website { get; set; }
public string BrandColor { get; set; }
public string LogoUrl { get; set; }
public string CssUrl { get; set; }
public string PaymentSoundUrl { get; set; }
public string SupportUrl { get; set; } public string SupportUrl { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter.Seconds))] [JsonConverter(typeof(TimeSpanJsonConverter.Seconds))]

View File

@@ -0,0 +1,61 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240508015052_fileid")]
public partial class fileid : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("""
UPDATE "Settings"
SET "Value" = jsonb_set(
"Value",
'{LogoUrl}',
to_jsonb('fileid:' || ("Value"->>'LogoFileId'))) - 'LogoFileId'
WHERE "Id" = 'BTCPayServer.Services.ThemeSettings'
AND "Value"->>'LogoFileId' IS NOT NULL;
UPDATE "Settings"
SET "Value" = jsonb_set(
"Value",
'{CustomThemeCssUrl}',
to_jsonb('fileid:' || ("Value"->>'CustomThemeFileId'))) - 'CustomThemeFileId'
WHERE "Id" = 'BTCPayServer.Services.ThemeSettings'
AND "Value"->>'CustomThemeFileId' IS NOT NULL;
UPDATE "Stores"
SET "StoreBlob" = jsonb_set(
"StoreBlob",
'{logoUrl}',
to_jsonb('fileid:' || ("StoreBlob"->>'logoFileId'))) - 'logoFileId'
WHERE "StoreBlob"->>'logoFileId' IS NOT NULL;
UPDATE "Stores"
SET "StoreBlob" = jsonb_set(
"StoreBlob",
'{cssUrl}',
to_jsonb('fileid:' || ("StoreBlob"->>'cssFileId'))) - 'cssFileId'
WHERE "StoreBlob"->>'cssFileId' IS NOT NULL;
UPDATE "Stores"
SET "StoreBlob" = jsonb_set(
"StoreBlob",
'{paymentSoundUrl}',
to_jsonb('fileid:' || ("StoreBlob"->>'soundFileId'))) - 'soundFileId'
WHERE "StoreBlob"->>'soundFileId' IS NOT NULL;
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -64,6 +64,11 @@ namespace BTCPayServer.Tests
get; get;
set; set;
} }
public Uri ServerUriWithIP
{
get;
set;
}
public string MySQL public string MySQL
{ {
@@ -164,6 +169,7 @@ namespace BTCPayServer.Tests
await File.WriteAllTextAsync(confPath, config.ToString()); await File.WriteAllTextAsync(confPath, config.ToString());
ServerUri = new Uri("http://" + HostName + ":" + Port + "/"); ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
ServerUriWithIP = new Uri("http://127.0.0.1:" + Port + "/");
HttpClient = new HttpClient(); HttpClient = new HttpClient();
HttpClient.BaseAddress = ServerUri; HttpClient.BaseAddress = ServerUri;
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");

View File

@@ -1366,12 +1366,24 @@ namespace BTCPayServer.Tests
var newStore = await client.CreateStore(new CreateStoreRequest { Name = "A" }); var newStore = await client.CreateStore(new CreateStoreRequest { Name = "A" });
Assert.Equal("A", newStore.Name); Assert.Equal("A", newStore.Name);
// validate
await AssertValidationError(["CssUrl", "LogoUrl", "BrandColor"], async () =>
await client.UpdateStore(newStore.Id, new UpdateStoreRequest
{
CssUrl = "style.css",
LogoUrl = "logo.svg",
BrandColor = "invalid"
}));
//update store //update store
Assert.Empty(newStore.PaymentMethodCriteria); Assert.Empty(newStore.PaymentMethodCriteria);
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest()); await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest
{ {
Name = "B", Name = "B",
CssUrl = "https://example.org/style.css",
LogoUrl = "https://example.org/logo.svg",
BrandColor = "#003366",
PaymentMethodCriteria = new List<PaymentMethodCriteriaData> PaymentMethodCriteria = new List<PaymentMethodCriteriaData>
{ {
new() new()
@@ -1384,6 +1396,9 @@ namespace BTCPayServer.Tests
} }
}); });
Assert.Equal("B", updatedStore.Name); Assert.Equal("B", updatedStore.Name);
Assert.Equal("https://example.org/style.css", updatedStore.CssUrl);
Assert.Equal("https://example.org/logo.svg", updatedStore.LogoUrl);
Assert.Equal("#003366", updatedStore.BrandColor);
var s = (await client.GetStore(newStore.Id)); var s = (await client.GetStore(newStore.Id));
Assert.Equal("B", s.Name); Assert.Equal("B", s.Name);
var pmc = Assert.Single(s.PaymentMethodCriteria); var pmc = Assert.Single(s.PaymentMethodCriteria);

View File

@@ -146,7 +146,7 @@ namespace BTCPayServer.Tests
public async Task ModifyPayment(Action<GeneralSettingsViewModel> modify) public async Task ModifyPayment(Action<GeneralSettingsViewModel> modify)
{ {
var storeController = GetController<UIStoresController>(); var storeController = GetController<UIStoresController>();
var response = storeController.GeneralSettings(); var response = await storeController.GeneralSettings();
GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model; GeneralSettingsViewModel settings = (GeneralSettingsViewModel)((ViewResult)response).Model;
modify(settings); modify(settings);
await storeController.GeneralSettings(settings); await storeController.GeneralSettings(settings);

View File

@@ -57,7 +57,6 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewResult>( Assert.IsType<ViewResult>(
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration)); await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
var shouldBeRedirectingToAzureStorageConfigPage = var shouldBeRedirectingToAzureStorageConfigPage =
Assert.IsType<RedirectToActionResult>(await controller.Storage()); Assert.IsType<RedirectToActionResult>(await controller.Storage());
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName); Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
@@ -72,9 +71,8 @@ namespace BTCPayServer.Tests
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString())) await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
.Model).ConnectionString); .Model).ConnectionString);
var fileId = await UnitTest1.CanUploadFile(controller);
await UnitTest1.CanRemoveFile(controller, fileId);
await UnitTest1.CanUploadRemoveFiles(controller);
} }
[Fact(Skip = "Fail on CI")] [Fact(Skip = "Fail on CI")]

View File

@@ -1,4 +1,5 @@
using System; using System;
using Dapper;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@@ -53,7 +54,6 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models; using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration; using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
using BTCPayServer.Storage.ViewModels; using BTCPayServer.Storage.ViewModels;
using ExchangeSharp;
using Fido2NetLib; using Fido2NetLib;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -79,6 +79,7 @@ using CreatePaymentRequestRequest = BTCPayServer.Client.Models.CreatePaymentRequ
using MarkPayoutRequest = BTCPayServer.Client.Models.MarkPayoutRequest; using MarkPayoutRequest = BTCPayServer.Client.Models.MarkPayoutRequest;
using PaymentRequestData = BTCPayServer.Client.Models.PaymentRequestData; using PaymentRequestData = BTCPayServer.Client.Models.PaymentRequestData;
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
using Microsoft.Extensions.Caching.Memory;
namespace BTCPayServer.Tests namespace BTCPayServer.Tests
{ {
@@ -297,7 +298,7 @@ namespace BTCPayServer.Tests
// Set tolerance to 50% // Set tolerance to 50%
var stores = user.GetController<UIStoresController>(); var stores = user.GetController<UIStoresController>();
var response = stores.GeneralSettings(); var response = await stores.GeneralSettings();
var vm = Assert.IsType<GeneralSettingsViewModel>(Assert.IsType<ViewResult>(response).Model); var vm = Assert.IsType<GeneralSettingsViewModel>(Assert.IsType<ViewResult>(response).Model);
Assert.Equal(0.0, vm.PaymentTolerance); Assert.Equal(0.0, vm.PaymentTolerance);
vm.PaymentTolerance = 50.0; vm.PaymentTolerance = 50.0;
@@ -439,7 +440,7 @@ namespace BTCPayServer.Tests
var user = tester.NewAccount(); var user = tester.NewAccount();
await user.GrantAccessAsync(true); await user.GrantAccessAsync(true);
var storeController = user.GetController<UIStoresController>(); var storeController = user.GetController<UIStoresController>();
var storeResponse = storeController.GeneralSettings(); var storeResponse = await storeController.GeneralSettings();
Assert.IsType<ViewResult>(storeResponse); Assert.IsType<ViewResult>(storeResponse);
Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC")); Assert.IsType<ViewResult>(storeController.SetupLightningNode(user.StoreId, "BTC"));
@@ -838,7 +839,7 @@ namespace BTCPayServer.Tests
var time = invoice.InvoiceTime; var time = invoice.InvoiceTime;
AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}"); AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}");
AssertSearchInvoice(acc, true, invoice.Id, $"enddate:{time.ToStringLowerInvariant()}"); AssertSearchInvoice(acc, true, invoice.Id, $"enddate:{time.ToString().ToLowerInvariant()}");
AssertSearchInvoice(acc, false, invoice.Id, AssertSearchInvoice(acc, false, invoice.Id,
$"startdate:{time.AddSeconds(1).ToString("yyyy-MM-dd HH:mm:ss")}"); $"startdate:{time.AddSeconds(1).ToString("yyyy-MM-dd HH:mm:ss")}");
AssertSearchInvoice(acc, false, invoice.Id, AssertSearchInvoice(acc, false, invoice.Id,
@@ -1499,8 +1500,7 @@ namespace BTCPayServer.Tests
var btcMethod = PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(); var btcMethod = PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString();
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice // We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
var vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert var vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
.IsType<ViewResult>(user.GetController<UIStoresController>().CheckoutAppearance()).Model);
Assert.Equal(2, vm.PaymentMethodCriteria.Count); Assert.Equal(2, vm.PaymentMethodCriteria.Count);
var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString())); var criteria = Assert.Single(vm.PaymentMethodCriteria.Where(m => m.PaymentMethod == btcMethod.ToString()));
Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), criteria.PaymentMethod); Assert.Equal(PaymentTypes.CHAIN.GetPaymentMethodId("BTC").ToString(), criteria.PaymentMethod);
@@ -1527,8 +1527,7 @@ namespace BTCPayServer.Tests
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963 // Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default // We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
// payment method should be LN. // payment method should be LN.
vm = Assert.IsType<CheckoutAppearanceViewModel>(Assert vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
.IsType<ViewResult>(user.GetController<UIStoresController>().CheckoutAppearance()).Model);
vm.DefaultPaymentMethod = lnMethod; vm.DefaultPaymentMethod = lnMethod;
criteria = vm.PaymentMethodCriteria.First(); criteria = vm.PaymentMethodCriteria.First();
criteria.Value = "150 USD"; criteria.Value = "150 USD";
@@ -1640,7 +1639,7 @@ namespace BTCPayServer.Tests
user.GrantAccess(true); user.GrantAccess(true);
user.RegisterLightningNode(cryptoCode); user.RegisterLightningNode(cryptoCode);
user.SetLNUrl(cryptoCode, false); user.SetLNUrl(cryptoCode, false);
var vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>(); var vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
var criteria = Assert.Single(vm.PaymentMethodCriteria); var criteria = Assert.Single(vm.PaymentMethodCriteria);
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod); Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod);
criteria.Value = "2 USD"; criteria.Value = "2 USD";
@@ -1660,7 +1659,7 @@ namespace BTCPayServer.Tests
// Activating LNUrl, we should still have only 1 payment criteria that can be set. // Activating LNUrl, we should still have only 1 payment criteria that can be set.
user.RegisterLightningNode(cryptoCode); user.RegisterLightningNode(cryptoCode);
user.SetLNUrl(cryptoCode, true); user.SetLNUrl(cryptoCode, true);
vm = user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModel<CheckoutAppearanceViewModel>(); vm = await user.GetController<UIStoresController>().CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
criteria = Assert.Single(vm.PaymentMethodCriteria); criteria = Assert.Single(vm.PaymentMethodCriteria);
Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod); Assert.Equal(PaymentTypes.LN.GetPaymentMethodId(cryptoCode).ToString(), criteria.PaymentMethod);
Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm).Result); Assert.IsType<RedirectToActionResult>(user.GetController<UIStoresController>().CheckoutAppearance(vm).Result);
@@ -2507,6 +2506,90 @@ namespace BTCPayServer.Tests
} }
} }
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanMigrateFileIds()
{
using var tester = CreateServerTester(newDb: true);
tester.DeleteStore = false;
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
using (var ctx = tester.PayTester.GetService<ApplicationDbContextFactory>().CreateContext())
{
var storeConfig = """
{
"spread": 0.0,
"cssFileId": "2a51c49a-9d54-4013-80a2-3f6e69d08523",
"logoFileId": "8f890691-87f9-4c65-80e5-3b7ffaa3551f",
"soundFileId": "62bc4757-b92b-4a3b-a8ab-0e9b693d6a29",
"networkFeeMode": "MultiplePaymentsOnly",
"defaultCurrency": "USD",
"showStoreHeader": true,
"celebratePayment": true,
"paymentTolerance": 0.0,
"invoiceExpiration": 15,
"preferredExchange": "kraken",
"showRecommendedFee": true,
"monitoringExpiration": 1440,
"showPayInWalletButton": true,
"displayExpirationTimer": 5,
"excludedPaymentMethods": null,
"recommendedFeeBlockTarget": 1
}
""";
var serverConfig = """
{
"CssUri": null,
"FirstRun": false,
"LogoFileId": "ce71d90a-dd90-40a3-b1f0-96d00c9abb52",
"CustomTheme": true,
"CustomThemeCssUri": null,
"CustomThemeFileId": "9b00f4ed-914b-437b-abd2-9a90c1b22c34",
"CustomThemeExtension": 0
}
""";
await ctx.Database.GetDbConnection().ExecuteAsync("""
UPDATE "Stores" SET "StoreBlob"=@storeConfig::JSONB WHERE "Id"=@storeId;
""", new { storeId = user.StoreId, storeConfig });
await ctx.Database.GetDbConnection().ExecuteAsync("""
UPDATE "Settings" SET "Value"=@serverConfig::JSONB WHERE "Id"='BTCPayServer.Services.ThemeSettings';
""", new { serverConfig });
await ctx.Database.GetDbConnection().ExecuteAsync("""
INSERT INTO "Files" VALUES (@id, @fileName, @id || '-' || @fileName, NOW(), @userId);
""",
new[]
{
new { id = "2a51c49a-9d54-4013-80a2-3f6e69d08523", fileName = "store.css", userId = user.UserId },
new { id = "8f890691-87f9-4c65-80e5-3b7ffaa3551f", fileName = "store.png", userId = user.UserId },
new { id = "ce71d90a-dd90-40a3-b1f0-96d00c9abb52", fileName = "admin.png", userId = user.UserId },
new { id = "9b00f4ed-914b-437b-abd2-9a90c1b22c34", fileName = "admin.css", userId = user.UserId },
new { id = "62bc4757-b92b-4a3b-a8ab-0e9b693d6a29", fileName = "store.mp3", userId = user.UserId },
});
await ctx.Database.GetDbConnection().ExecuteAsync("""
DELETE FROM "__EFMigrationsHistory" WHERE "MigrationId"='20240508015052_fileid'
""");
await ctx.Database.MigrateAsync();
((MemoryCache)tester.PayTester.GetService<IMemoryCache>()).Clear();
}
var controller = tester.PayTester.GetController<UIStoresController>(user.UserId, user.StoreId);
var vm = await controller.GeneralSettings().AssertViewModelAsync<GeneralSettingsViewModel>();
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/8f890691-87f9-4c65-80e5-3b7ffaa3551f-store.png", vm.LogoUrl);
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/2a51c49a-9d54-4013-80a2-3f6e69d08523-store.css", vm.CssUrl);
var vm2 = await controller.CheckoutAppearance().AssertViewModelAsync<CheckoutAppearanceViewModel>();
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/62bc4757-b92b-4a3b-a8ab-0e9b693d6a29-store.mp3", vm2.PaymentSoundUrl);
var serverController = tester.PayTester.GetController<UIServerController>();
var branding = await serverController.Branding().AssertViewModelAsync<BrandingViewModel>();
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/ce71d90a-dd90-40a3-b1f0-96d00c9abb52-admin.png", branding.LogoUrl);
Assert.Equal(tester.PayTester.ServerUriWithIP + "LocalStorage/9b00f4ed-914b-437b-abd2-9a90c1b22c34-admin.css", branding.CustomThemeCssUrl);
}
[Fact(Timeout = LongRunningTestTimeout)] [Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")] [Trait("Integration", "Integration")]
public async Task CanDoLightningInternalNodeMigration() public async Task CanDoLightningInternalNodeMigration()
@@ -2943,14 +3026,14 @@ namespace BTCPayServer.Tests
Assert.Equal(StorageProvider.FileSystem, Assert.Equal(StorageProvider.FileSystem,
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]); shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
await CanUploadRemoveFiles(controller); var fileId = await CanUploadFile(controller);
await CanRemoveFile(controller, fileId);
} }
internal static async Task CanUploadRemoveFiles(UIServerController controller) internal static async Task<string> CanUploadFile(UIServerController controller)
{ {
var fileContent = "content"; var fileContent = "content";
List<IFormFile> fileList = new List<IFormFile>(); var fileList = new List<IFormFile> { TestUtils.GetFormFile("uploadtestfile1.txt", fileContent) };
fileList.Add(TestUtils.GetFormFile("uploadtestfile1.txt", fileContent));
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFiles(fileList)); var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFiles(fileList));
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileIds")); Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileIds"));
@@ -2966,7 +3049,6 @@ namespace BTCPayServer.Tests
Assert.True(viewFilesViewModel.DirectUrlByFiles.ContainsKey(fileId)); Assert.True(viewFilesViewModel.DirectUrlByFiles.ContainsKey(fileId));
Assert.NotEmpty(viewFilesViewModel.DirectUrlByFiles[fileId]); Assert.NotEmpty(viewFilesViewModel.DirectUrlByFiles[fileId]);
//verify file is available and the same //verify file is available and the same
using var net = new HttpClient(); using var net = new HttpClient();
var data = await net.GetStringAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId])); var data = await net.GetStringAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId]));
@@ -2991,17 +3073,20 @@ namespace BTCPayServer.Tests
data = await net.GetStringAsync(new Uri(url)); data = await net.GetStringAsync(new Uri(url));
Assert.Equal(fileContent, data); Assert.Equal(fileContent, data);
return fileId;
}
internal static async Task CanRemoveFile(UIServerController controller, string fileId)
{
//delete file //delete file
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId)); Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
statusMessageModel = controller.TempData.GetStatusMessageModel(); var statusMessageModel = controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel); Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity); Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
//attempt to fetch deleted file //attempt to fetch deleted file
viewFilesViewModel = var viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model); Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files([fileId])).Model);
Assert.Null(viewFilesViewModel.DirectUrlByFiles); Assert.Null(viewFilesViewModel.DirectUrlByFiles);
} }

View File

@@ -1,13 +1,12 @@
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Abstractions.Contracts
@inject ThemeSettings Theme @inject ThemeSettings Theme
@inject IFileService FileService @inject UriResolver UriResolver
@model BTCPayServer.Components.MainLogo.MainLogoViewModel @model BTCPayServer.Components.MainLogo.MainLogoViewModel
@if (!string.IsNullOrEmpty(Theme.LogoFileId)) @if (Theme.LogoUrl is not null)
{ {
var logoSrc = await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Theme.LogoFileId); var logoUrl = await UriResolver.Resolve(this.Context.Request.GetAbsoluteRootUri(), Theme.LogoUrl);
<img src="@logoSrc" alt="BTCPay Server" class="main-logo main-logo-custom @Model.CssClass" /> <img src="@logoUrl" alt="BTCPay Server" class="main-logo main-logo-custom @Model.CssClass" />
} }
else else
{ {

View File

@@ -1,11 +1,8 @@
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using BTCPayServer.Components.MainLogo @using BTCPayServer.Components.MainLogo
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Views.Server @using BTCPayServer.Views.Server
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@inject BTCPayServerEnvironment Env @inject BTCPayServerEnvironment Env
@inject IFileService FileService
@model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel @model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel
@functions { @functions {
@* ReSharper disable once CSharpWarnings::CS1998 *@ @* ReSharper disable once CSharpWarnings::CS1998 *@
@@ -39,9 +36,9 @@ else
<div id="StoreSelector"> <div id="StoreSelector">
<div id="StoreSelectorDropdown" class="dropdown only-for-js"> <div id="StoreSelectorDropdown" class="dropdown only-for-js">
<button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill px-3 @(Model.CurrentStoreId == null ? "empty-state" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill px-3 @(Model.CurrentStoreId == null ? "empty-state" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false">
@if (!string.IsNullOrEmpty(Model.CurrentStoreLogoFileId)) @if (!string.IsNullOrEmpty(Model.CurrentStoreLogoUrl))
{ {
<img class="logo" src="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.CurrentStoreLogoFileId))" alt="@Model.CurrentDisplayName" /> <img class="logo" src="@Model.CurrentStoreLogoUrl" alt="@Model.CurrentDisplayName" />
} }
else else
{ {

View File

@@ -1,8 +1,11 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@@ -14,13 +17,16 @@ namespace BTCPayServer.Components.StoreSelector
public class StoreSelector : ViewComponent public class StoreSelector : ViewComponent
{ {
private readonly StoreRepository _storeRepo; private readonly StoreRepository _storeRepo;
private readonly UriResolver _uriResolver;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
public StoreSelector( public StoreSelector(
StoreRepository storeRepo, StoreRepository storeRepo,
UriResolver uriResolver,
UserManager<ApplicationUser> userManager) UserManager<ApplicationUser> userManager)
{ {
_storeRepo = storeRepo; _storeRepo = storeRepo;
_uriResolver = uriResolver;
_userManager = userManager; _userManager = userManager;
} }
@@ -50,7 +56,7 @@ namespace BTCPayServer.Components.StoreSelector
Options = options, Options = options,
CurrentStoreId = currentStore?.Id, CurrentStoreId = currentStore?.Id,
CurrentDisplayName = currentStore?.StoreName, CurrentDisplayName = currentStore?.StoreName,
CurrentStoreLogoFileId = blob?.LogoFileId, CurrentStoreLogoUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), blob?.LogoUrl),
ArchivedCount = archivedCount ArchivedCount = archivedCount
}; };

View File

@@ -7,7 +7,7 @@ namespace BTCPayServer.Components.StoreSelector
{ {
public List<StoreSelectorOption> Options { get; set; } public List<StoreSelectorOption> Options { get; set; }
public string CurrentStoreId { get; set; } public string CurrentStoreId { get; set; }
public string CurrentStoreLogoFileId { get; set; } public string CurrentStoreLogoUrl { get; set; }
public string CurrentDisplayName { get; set; } public string CurrentDisplayName { get; set; }
public int ArchivedCount { get; set; } public int ArchivedCount { get; set; }
} }

View File

@@ -233,9 +233,7 @@ namespace BTCPayServer.Controllers.Greenfield
Description = request.Description?.Trim(), Description = request.Description?.Trim(),
EndDate = request.EndDate?.UtcDateTime, EndDate = request.EndDate?.UtcDateTime,
TargetAmount = request.TargetAmount, TargetAmount = request.TargetAmount,
CustomCSSLink = request.CustomCSSLink?.Trim(),
MainImageUrl = request.MainImageUrl?.Trim(), MainImageUrl = request.MainImageUrl?.Trim(),
EmbeddedCSS = request.EmbeddedCSS?.Trim(),
NotificationUrl = request.NotificationUrl?.Trim(), NotificationUrl = request.NotificationUrl?.Trim(),
Tagline = request.Tagline?.Trim(), Tagline = request.Tagline?.Trim(),
PerksTemplate = request.PerksTemplate is not null ? AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate.Trim())) : null, PerksTemplate = request.PerksTemplate is not null ? AppService.SerializeTemplate(AppService.Parse(request.PerksTemplate.Trim())) : null,
@@ -272,11 +270,9 @@ namespace BTCPayServer.Controllers.Greenfield
ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF, ButtonText = request.FixedAmountPayButtonText ?? PointOfSaleSettings.BUTTON_TEXT_DEF,
CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF, CustomButtonText = request.CustomAmountPayButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF, CustomTipText = request.TipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
CustomCSSLink = request.CustomCSSLink,
NotificationUrl = request.NotificationUrl, NotificationUrl = request.NotificationUrl,
RedirectUrl = request.RedirectUrl, RedirectUrl = request.RedirectUrl,
Description = request.Description, Description = request.Description,
EmbeddedCSS = request.EmbeddedCSS,
RedirectAutomatically = request.RedirectAutomatically, RedirectAutomatically = request.RedirectAutomatically,
FormId = request.FormId FormId = request.FormId
}; };
@@ -341,11 +337,9 @@ namespace BTCPayServer.Controllers.Greenfield
FixedAmountPayButtonText = settings.ButtonText, FixedAmountPayButtonText = settings.ButtonText,
CustomAmountPayButtonText = settings.CustomButtonText, CustomAmountPayButtonText = settings.CustomButtonText,
TipText = settings.CustomTipText, TipText = settings.CustomTipText,
CustomCSSLink = settings.CustomCSSLink,
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
RedirectUrl = settings.RedirectUrl, RedirectUrl = settings.RedirectUrl,
Description = settings.Description, Description = settings.Description,
EmbeddedCSS = settings.EmbeddedCSS,
RedirectAutomatically = settings.RedirectAutomatically ?? false, RedirectAutomatically = settings.RedirectAutomatically ?? false,
}; };
} }
@@ -399,9 +393,7 @@ namespace BTCPayServer.Controllers.Greenfield
Description = settings.Description, Description = settings.Description,
EndDate = settings.EndDate, EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount, TargetAmount = settings.TargetAmount,
CustomCSSLink = settings.CustomCSSLink,
MainImageUrl = settings.MainImageUrl, MainImageUrl = settings.MainImageUrl,
EmbeddedCSS = settings.EmbeddedCSS,
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
Tagline = settings.Tagline, Tagline = settings.Tagline,
Perks = JsonConvert.DeserializeObject( Perks = JsonConvert.DeserializeObject(

View File

@@ -234,9 +234,6 @@ namespace BTCPayServer.Controllers.Greenfield
if (string.IsNullOrEmpty(data.Title)) if (string.IsNullOrEmpty(data.Title))
ModelState.AddModelError(nameof(data.Title), "Title is required"); ModelState.AddModelError(nameof(data.Title), "Title is required");
if (!string.IsNullOrEmpty(data.CustomCSSLink) && data.CustomCSSLink.Length > 500)
ModelState.AddModelError(nameof(data.CustomCSSLink), "CustomCSSLink is 500 chars max");
return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null; return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null;
} }
@@ -257,8 +254,6 @@ namespace BTCPayServer.Controllers.Greenfield
ExpiryDate = blob.ExpiryDate, ExpiryDate = blob.ExpiryDate,
Email = blob.Email, Email = blob.Email,
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts, AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts,
EmbeddedCSS = blob.EmbeddedCSS,
CustomCSSLink = blob.CustomCSSLink,
FormResponse = blob.FormResponse, FormResponse = blob.FormResponse,
FormId = blob.FormId FormId = blob.FormId
}; };

View File

@@ -116,6 +116,10 @@ namespace BTCPayServer.Controllers.Greenfield
Name = data.StoreName, Name = data.StoreName,
Website = data.StoreWebsite, Website = data.StoreWebsite,
Archived = data.Archived, Archived = data.Archived,
BrandColor = storeBlob.BrandColor,
CssUrl = storeBlob.CssUrl?.ToString(),
LogoUrl = storeBlob.LogoUrl?.ToString(),
PaymentSoundUrl = storeBlob.PaymentSoundUrl?.ToString(),
SupportUrl = storeBlob.StoreSupportUrl, SupportUrl = storeBlob.StoreSupportUrl,
SpeedPolicy = data.SpeedPolicy, SpeedPolicy = data.SpeedPolicy,
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(), DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(),
@@ -192,6 +196,10 @@ namespace BTCPayServer.Controllers.Greenfield
blob.LightningDescriptionTemplate = restModel.LightningDescriptionTemplate; blob.LightningDescriptionTemplate = restModel.LightningDescriptionTemplate;
blob.PaymentTolerance = restModel.PaymentTolerance; blob.PaymentTolerance = restModel.PaymentTolerance;
blob.PayJoinEnabled = restModel.PayJoinEnabled; blob.PayJoinEnabled = restModel.PayJoinEnabled;
blob.BrandColor = restModel.BrandColor;
blob.LogoUrl = restModel.LogoUrl is null ? null : UnresolvedUri.Create(restModel.LogoUrl);
blob.CssUrl = restModel.CssUrl is null ? null : UnresolvedUri.Create(restModel.CssUrl);
blob.PaymentSoundUrl = restModel.PaymentSoundUrl is null ? null : UnresolvedUri.Create(restModel.PaymentSoundUrl);
if (restModel.AutoDetectLanguage.HasValue) if (restModel.AutoDetectLanguage.HasValue)
blob.AutoDetectLanguage = restModel.AutoDetectLanguage.Value; blob.AutoDetectLanguage = restModel.AutoDetectLanguage.Value;
if (restModel.ShowPayInWalletButton.HasValue) if (restModel.ShowPayInWalletButton.HasValue)
@@ -237,6 +245,18 @@ namespace BTCPayServer.Controllers.Greenfield
{ {
ModelState.AddModelError(nameof(request.Website), "Website is not a valid url"); ModelState.AddModelError(nameof(request.Website), "Website is not a valid url");
} }
if (!string.IsNullOrEmpty(request.LogoUrl) && !Uri.TryCreate(request.LogoUrl, UriKind.Absolute, out _))
{
ModelState.AddModelError(nameof(request.LogoUrl), "Logo is not a valid url");
}
if (!string.IsNullOrEmpty(request.CssUrl) && !Uri.TryCreate(request.CssUrl, UriKind.Absolute, out _))
{
ModelState.AddModelError(nameof(request.CssUrl), "CSS is not a valid url");
}
if (!string.IsNullOrEmpty(request.BrandColor) && !ColorPalette.IsValid(request.BrandColor))
{
ModelState.AddModelError(nameof(request.BrandColor), "Brand color is not a valid HEX Color (e.g. #F7931A)");
}
if (request.InvoiceExpiration < TimeSpan.FromMinutes(1) && request.InvoiceExpiration > TimeSpan.FromMinutes(60 * 24 * 24)) if (request.InvoiceExpiration < TimeSpan.FromMinutes(1) && request.InvoiceExpiration > TimeSpan.FromMinutes(60 * 24 * 24))
ModelState.AddModelError(nameof(request.InvoiceExpiration), "InvoiceExpiration can only be between 1 and 34560 mins"); ModelState.AddModelError(nameof(request.InvoiceExpiration), "InvoiceExpiration can only be between 1 and 34560 mins");
if (request.DisplayExpirationTimer < TimeSpan.FromMinutes(1) && request.DisplayExpirationTimer > TimeSpan.FromMinutes(60 * 24 * 24)) if (request.DisplayExpirationTimer < TimeSpan.FromMinutes(1) && request.DisplayExpirationTimer > TimeSpan.FromMinutes(60 * 24 * 24))

View File

@@ -219,7 +219,7 @@ namespace BTCPayServer.Controllers
Currency = i.Currency, Currency = i.Currency,
Timestamp = i.InvoiceTime, Timestamp = i.InvoiceTime,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob), StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob),
ReceiptOptions = receipt ReceiptOptions = receipt
}; };
@@ -889,7 +889,7 @@ namespace BTCPayServer.Controllers
DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en", DefaultLang = lang ?? invoice.DefaultLanguage ?? storeBlob.DefaultLang ?? "en",
ShowPayInWalletButton = storeBlob.ShowPayInWalletButton, ShowPayInWalletButton = storeBlob.ShowPayInWalletButton,
ShowStoreHeader = storeBlob.ShowStoreHeader, ShowStoreHeader = storeBlob.ShowStoreHeader,
StoreBranding = new StoreBrandingViewModel(storeBlob), StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob),
HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice", HtmlTitle = storeBlob.HtmlTitle ?? "BTCPay Invoice",
CelebratePayment = storeBlob.CelebratePayment, CelebratePayment = storeBlob.CelebratePayment,
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback, OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
@@ -978,9 +978,9 @@ namespace BTCPayServer.Controllers
if (storeBlob.PlaySoundOnPayment) if (storeBlob.PlaySoundOnPayment)
{ {
model.PaymentSoundUrl = string.IsNullOrEmpty(storeBlob.SoundFileId) model.PaymentSoundUrl = storeBlob.PaymentSoundUrl is null
? string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/payment.mp3") ? string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/payment.mp3")
: await _fileService.GetFileUrl(Request.GetAbsoluteRootUri(), storeBlob.SoundFileId); : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.PaymentSoundUrl);
model.ErrorSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/error.mp3"); model.ErrorSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/error.mp3");
model.NfcReadSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/nfcread.mp3"); model.NfcReadSoundUrl = string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/nfcread.mp3");
} }

View File

@@ -65,6 +65,7 @@ namespace BTCPayServer.Controllers
private readonly PaymentMethodViewProvider _viewProvider; private readonly PaymentMethodViewProvider _viewProvider;
private readonly AppService _appService; private readonly AppService _appService;
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly UriResolver _uriResolver;
public WebhookSender WebhookNotificationManager { get; } public WebhookSender WebhookNotificationManager { get; }
@@ -91,6 +92,7 @@ namespace BTCPayServer.Controllers
LinkGenerator linkGenerator, LinkGenerator linkGenerator,
AppService appService, AppService appService,
IFileService fileService, IFileService fileService,
UriResolver uriResolver,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
TransactionLinkProviders transactionLinkProviders, TransactionLinkProviders transactionLinkProviders,
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions, Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
@@ -120,6 +122,7 @@ namespace BTCPayServer.Controllers
_paymentModelExtensions = paymentModelExtensions; _paymentModelExtensions = paymentModelExtensions;
_viewProvider = viewProvider; _viewProvider = viewProvider;
_fileService = fileService; _fileService = fileService;
_uriResolver = uriResolver;
_appService = appService; _appService = appService;
} }

View File

@@ -41,6 +41,7 @@ namespace BTCPayServer.Controllers
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
private readonly InvoiceRepository _InvoiceRepository; private readonly InvoiceRepository _InvoiceRepository;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly UriResolver _uriResolver;
private readonly BTCPayNetworkProvider _networkProvider; private readonly BTCPayNetworkProvider _networkProvider;
private FormComponentProviders FormProviders { get; } private FormComponentProviders FormProviders { get; }
@@ -55,6 +56,7 @@ namespace BTCPayServer.Controllers
CurrencyNameTable currencies, CurrencyNameTable currencies,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
StoreRepository storeRepository, StoreRepository storeRepository,
UriResolver uriResolver,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
FormComponentProviders formProviders, FormComponentProviders formProviders,
FormDataService formDataService, FormDataService formDataService,
@@ -68,6 +70,7 @@ namespace BTCPayServer.Controllers
_Currencies = currencies; _Currencies = currencies;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_uriResolver = uriResolver;
_InvoiceRepository = invoiceRepository; _InvoiceRepository = invoiceRepository;
FormProviders = formProviders; FormProviders = formProviders;
FormDataService = formDataService; FormDataService = formDataService;
@@ -191,8 +194,6 @@ namespace BTCPayServer.Controllers
blob.Amount = viewModel.Amount; blob.Amount = viewModel.Amount;
blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime(); blob.ExpiryDate = viewModel.ExpiryDate?.ToUniversalTime();
blob.Currency = viewModel.Currency ?? store.GetStoreBlob().DefaultCurrency; blob.Currency = viewModel.Currency ?? store.GetStoreBlob().DefaultCurrency;
blob.EmbeddedCSS = viewModel.EmbeddedCSS;
blob.CustomCSSLink = viewModel.CustomCSSLink;
blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts; blob.AllowCustomPaymentAmounts = viewModel.AllowCustomPaymentAmounts;
blob.FormId = viewModel.FormId; blob.FormId = viewModel.FormId;
@@ -229,11 +230,7 @@ namespace BTCPayServer.Controllers
vm.HubPath = PaymentRequestHub.GetHubPath(Request); vm.HubPath = PaymentRequestHub.GetHubPath(Request);
vm.StoreName = store.StoreName; vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite; vm.StoreWebsite = store.StoreWebsite;
vm.StoreBranding = new StoreBrandingViewModel(storeBlob) vm.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
{
EmbeddedCSS = vm.EmbeddedCSS,
CustomCSSLink = vm.CustomCSSLink
};
return View(vm); return View(vm);
} }
@@ -290,11 +287,8 @@ namespace BTCPayServer.Controllers
viewModel.Form = form; viewModel.Form = form;
var storeBlob = result.StoreData.GetStoreBlob(); var storeBlob = result.StoreData.GetStoreBlob();
viewModel.StoreBranding = new StoreBrandingViewModel(storeBlob) viewModel.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
{
EmbeddedCSS = prBlob.EmbeddedCSS,
CustomCSSLink = prBlob.CustomCSSLink
};
return View("Views/UIForms/View", viewModel); return View("Views/UIForms/View", viewModel);
} }

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -21,16 +22,19 @@ namespace BTCPayServer.Controllers
{ {
private readonly BTCPayNetworkProvider _BtcPayNetworkProvider; private readonly BTCPayNetworkProvider _BtcPayNetworkProvider;
private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions; private readonly Dictionary<PaymentMethodId, IPaymentModelExtension> _paymentModelExtensions;
private readonly UriResolver _uriResolver;
private readonly PaymentMethodHandlerDictionary _handlers; private readonly PaymentMethodHandlerDictionary _handlers;
private readonly StoreRepository _StoreRepository; private readonly StoreRepository _StoreRepository;
public UIPublicLightningNodeInfoController(BTCPayNetworkProvider btcPayNetworkProvider, public UIPublicLightningNodeInfoController(BTCPayNetworkProvider btcPayNetworkProvider,
Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions, Dictionary<PaymentMethodId, IPaymentModelExtension> paymentModelExtensions,
UriResolver uriResolver,
PaymentMethodHandlerDictionary handlers, PaymentMethodHandlerDictionary handlers,
StoreRepository storeRepository) StoreRepository storeRepository)
{ {
_BtcPayNetworkProvider = btcPayNetworkProvider; _BtcPayNetworkProvider = btcPayNetworkProvider;
_paymentModelExtensions = paymentModelExtensions; _paymentModelExtensions = paymentModelExtensions;
_uriResolver = uriResolver;
_handlers = handlers; _handlers = handlers;
_StoreRepository = storeRepository; _StoreRepository = storeRepository;
} }
@@ -49,7 +53,7 @@ namespace BTCPayServer.Controllers
{ {
CryptoCode = cryptoCode, CryptoCode = cryptoCode,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob) StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob)
}; };
try try
{ {

View File

@@ -40,6 +40,7 @@ namespace BTCPayServer.Controllers
private readonly ApplicationDbContextFactory _dbContextFactory; private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly CurrencyNameTable _currencyNameTable; private readonly CurrencyNameTable _currencyNameTable;
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
private readonly UriResolver _uriResolver;
private readonly PullPaymentHostedService _pullPaymentHostedService; private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkProvider _networkProvider; private readonly BTCPayNetworkProvider _networkProvider;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings; private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
@@ -51,6 +52,7 @@ namespace BTCPayServer.Controllers
public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory, public UIPullPaymentController(ApplicationDbContextFactory dbContextFactory,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
DisplayFormatter displayFormatter, DisplayFormatter displayFormatter,
UriResolver uriResolver,
PullPaymentHostedService pullPaymentHostedService, PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
BTCPayNetworkJsonSerializerSettings serializerSettings, BTCPayNetworkJsonSerializerSettings serializerSettings,
@@ -62,6 +64,7 @@ namespace BTCPayServer.Controllers
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_currencyNameTable = currencyNameTable; _currencyNameTable = currencyNameTable;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
_uriResolver = uriResolver;
_pullPaymentHostedService = pullPaymentHostedService; _pullPaymentHostedService = pullPaymentHostedService;
_serializerSettings = serializerSettings; _serializerSettings = serializerSettings;
_payoutHandlers = payoutHandlers; _payoutHandlers = payoutHandlers;
@@ -121,11 +124,7 @@ namespace BTCPayServer.Controllers
}).ToList() }).ToList()
}; };
vm.IsPending &= vm.AmountDue > 0.0m; vm.IsPending &= vm.AmountDue > 0.0m;
vm.StoreBranding = new StoreBrandingViewModel(storeBlob) vm.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
{
EmbeddedCSS = blob.View.EmbeddedCSS,
CustomCSSLink = blob.View.CustomCSSLink
};
if (_pullPaymentHostedService.SupportsLNURL(blob)) if (_pullPaymentHostedService.SupportsLNURL(blob))
{ {
@@ -185,13 +184,11 @@ namespace BTCPayServer.Controllers
var blob = pp.GetBlob(); var blob = pp.GetBlob();
blob.Description = viewModel.Description ?? string.Empty; blob.Description = viewModel.Description ?? string.Empty;
blob.Name = viewModel.Name ?? string.Empty; blob.Name = viewModel.Name ?? string.Empty;
blob.View = new PullPaymentBlob.PullPaymentView() blob.View = new PullPaymentBlob.PullPaymentView
{ {
Title = viewModel.Name ?? string.Empty, Title = viewModel.Name ?? string.Empty,
Description = viewModel.Description ?? string.Empty, Description = viewModel.Description ?? string.Empty,
CustomCSSLink = viewModel.CustomCSSLink, Email = null
Email = null,
EmbeddedCSS = viewModel.EmbeddedCSS,
}; };
pp.SetBlob(blob); pp.SetBlob(blob);

View File

@@ -65,6 +65,7 @@ namespace BTCPayServer.Controllers
private readonly IFileService _fileService; private readonly IFileService _fileService;
private readonly IEnumerable<IStorageProviderService> _StorageProviderServices; private readonly IEnumerable<IStorageProviderService> _StorageProviderServices;
private readonly LinkGenerator _linkGenerator; private readonly LinkGenerator _linkGenerator;
private readonly UriResolver _uriResolver;
private readonly EmailSenderFactory _emailSenderFactory; private readonly EmailSenderFactory _emailSenderFactory;
private readonly TransactionLinkProviders _transactionLinkProviders; private readonly TransactionLinkProviders _transactionLinkProviders;
@@ -88,6 +89,7 @@ namespace BTCPayServer.Controllers
IOptions<ExternalServicesOptions> externalServiceOptions, IOptions<ExternalServicesOptions> externalServiceOptions,
Logs logs, Logs logs,
LinkGenerator linkGenerator, LinkGenerator linkGenerator,
UriResolver uriResolver,
EmailSenderFactory emailSenderFactory, EmailSenderFactory emailSenderFactory,
IHostApplicationLifetime applicationLifetime, IHostApplicationLifetime applicationLifetime,
IHtmlHelper html, IHtmlHelper html,
@@ -113,6 +115,7 @@ namespace BTCPayServer.Controllers
_externalServiceOptions = externalServiceOptions; _externalServiceOptions = externalServiceOptions;
Logs = logs; Logs = logs;
_linkGenerator = linkGenerator; _linkGenerator = linkGenerator;
_uriResolver = uriResolver;
_emailSenderFactory = emailSenderFactory; _emailSenderFactory = emailSenderFactory;
ApplicationLifetime = applicationLifetime; ApplicationLifetime = applicationLifetime;
Html = html; Html = html;
@@ -1025,9 +1028,8 @@ namespace BTCPayServer.Controllers
ContactUrl = server.ContactUrl, ContactUrl = server.ContactUrl,
CustomTheme = theme.CustomTheme, CustomTheme = theme.CustomTheme,
CustomThemeExtension = theme.CustomThemeExtension, CustomThemeExtension = theme.CustomThemeExtension,
CustomThemeCssUri = theme.CustomThemeCssUri, CustomThemeCssUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), theme.CustomThemeCssUrl),
CustomThemeFileId = theme.CustomThemeFileId, LogoUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), theme.LogoUrl)
LogoFileId = theme.LogoFileId
}; };
return View(vm); return View(vm);
} }
@@ -1046,8 +1048,8 @@ namespace BTCPayServer.Controllers
if (userId is null) if (userId is null)
return NotFound(); return NotFound();
vm.LogoFileId = theme.LogoFileId; vm.LogoUrl = await _uriResolver.Resolve(this.Request.GetAbsoluteRootUri(), theme.LogoUrl);
vm.CustomThemeFileId = theme.CustomThemeFileId; vm.CustomThemeCssUrl = await _uriResolver.Resolve(this.Request.GetAbsoluteRootUri(), theme.CustomThemeCssUrl);
if (server.ServerName != vm.ServerName) if (server.ServerName != vm.ServerName)
{ {
@@ -1072,17 +1074,12 @@ namespace BTCPayServer.Controllers
{ {
if (vm.CustomThemeFile.ContentType.Equals("text/css", StringComparison.InvariantCulture)) if (vm.CustomThemeFile.ContentType.Equals("text/css", StringComparison.InvariantCulture))
{ {
// delete existing file
if (!string.IsNullOrEmpty(theme.CustomThemeFileId))
{
await _fileService.RemoveFile(theme.CustomThemeFileId, userId);
}
// add new file // add new file
try try
{ {
var storedFile = await _fileService.AddFile(vm.CustomThemeFile, userId); var storedFile = await _fileService.AddFile(vm.CustomThemeFile, userId);
vm.CustomThemeFileId = theme.CustomThemeFileId = storedFile.Id; theme.CustomThemeCssUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
vm.CustomThemeCssUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), theme.CustomThemeCssUrl);
settingsChanged = true; settingsChanged = true;
} }
catch (Exception e) catch (Exception e)
@@ -1095,10 +1092,12 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.CustomThemeFile), "The uploaded theme file needs to be a CSS file"); ModelState.AddModelError(nameof(vm.CustomThemeFile), "The uploaded theme file needs to be a CSS file");
} }
} }
else if (RemoveCustomThemeFile && !string.IsNullOrEmpty(theme.CustomThemeFileId)) else if (RemoveCustomThemeFile && theme.CustomThemeCssUrl is not null)
{ {
await _fileService.RemoveFile(theme.CustomThemeFileId, userId); vm.CustomThemeCssUrl = null;
vm.CustomThemeFileId = theme.CustomThemeFileId = null; theme.CustomThemeCssUrl = null;
theme.CustomTheme = false;
theme.CustomThemeExtension = ThemeExtension.Custom;
settingsChanged = true; settingsChanged = true;
} }
@@ -1122,16 +1121,12 @@ namespace BTCPayServer.Controllers
else else
{ {
vm.LogoFile = formFile; vm.LogoFile = formFile;
// delete existing file
if (!string.IsNullOrEmpty(theme.LogoFileId))
{
await _fileService.RemoveFile(theme.LogoFileId, userId);
}
// add new file // add new file
try try
{ {
var storedFile = await _fileService.AddFile(vm.LogoFile, userId); var storedFile = await _fileService.AddFile(vm.LogoFile, userId);
vm.LogoFileId = theme.LogoFileId = storedFile.Id; theme.LogoUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
vm.LogoUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), theme.LogoUrl);
settingsChanged = true; settingsChanged = true;
} }
catch (Exception e) catch (Exception e)
@@ -1141,29 +1136,19 @@ namespace BTCPayServer.Controllers
} }
} }
} }
else if (RemoveLogoFile && !string.IsNullOrEmpty(theme.LogoFileId)) else if (RemoveLogoFile && theme.LogoUrl is not null)
{ {
await _fileService.RemoveFile(theme.LogoFileId, userId); vm.LogoUrl = null;
vm.LogoFileId = theme.LogoFileId = null; theme.LogoUrl = null;
settingsChanged = true; settingsChanged = true;
} }
if (vm.CustomTheme && !string.IsNullOrEmpty(vm.CustomThemeCssUri) && !Uri.IsWellFormedUriString(vm.CustomThemeCssUri, UriKind.RelativeOrAbsolute)) if (vm.CustomTheme && theme.CustomThemeExtension != vm.CustomThemeExtension)
{
ModelState.AddModelError(nameof(theme.CustomThemeCssUri), "Please provide a non-empty theme URI");
}
else if (theme.CustomThemeCssUri != vm.CustomThemeCssUri)
{
theme.CustomThemeCssUri = vm.CustomThemeCssUri;
settingsChanged = true;
}
if (theme.CustomThemeExtension != vm.CustomThemeExtension)
{ {
// Require a custom theme to be defined in that case // Require a custom theme to be defined in that case
if (string.IsNullOrEmpty(vm.CustomThemeCssUri) && string.IsNullOrEmpty(theme.CustomThemeFileId)) if (string.IsNullOrEmpty(vm.CustomThemeCssUrl) && theme.CustomThemeCssUrl is null)
{ {
ModelState.AddModelError(nameof(vm.CustomThemeFile), "Please provide a custom theme"); ModelState.AddModelError(nameof(vm.CustomThemeCssUrl), "Please provide a custom theme");
} }
else else
{ {
@@ -1172,7 +1157,7 @@ namespace BTCPayServer.Controllers
} }
} }
if (theme.CustomTheme != vm.CustomTheme) if (theme.CustomTheme != vm.CustomTheme && !RemoveCustomThemeFile)
{ {
theme.CustomTheme = vm.CustomTheme; theme.CustomTheme = vm.CustomTheme;
settingsChanged = true; settingsChanged = true;
@@ -1182,6 +1167,7 @@ namespace BTCPayServer.Controllers
{ {
await _SettingsRepository.UpdateSetting(theme); await _SettingsRepository.UpdateSetting(theme);
TempData[WellKnownTempData.SuccessMessage] = "Settings updated successfully"; TempData[WellKnownTempData.SuccessMessage] = "Settings updated successfully";
return RedirectToAction(nameof(Branding));
} }
return View(vm); return View(vm);

View File

@@ -95,8 +95,6 @@ namespace BTCPayServer.Controllers
{ {
Name = "", Name = "",
Currency = CurrentStore.GetStoreBlob().DefaultCurrency, Currency = CurrentStore.GetStoreBlob().DefaultCurrency,
CustomCSSLink = "",
EmbeddedCSS = "",
PayoutMethodsItem = PayoutMethodsItem =
paymentMethods.Select(id => new SelectListItem(id.ToString(), id.ToString(), true)) paymentMethods.Select(id => new SelectListItem(id.ToString(), id.ToString(), true))
}); });
@@ -145,7 +143,7 @@ namespace BTCPayServer.Controllers
return View(model); return View(model);
model.AutoApproveClaims = model.AutoApproveClaims && (await model.AutoApproveClaims = model.AutoApproveClaims && (await
_authorizationService.AuthorizeAsync(User, storeId, Policies.CanCreatePullPayments)).Succeeded; _authorizationService.AuthorizeAsync(User, storeId, Policies.CanCreatePullPayments)).Succeeded;
await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment() await _pullPaymentService.CreatePullPayment(new CreatePullPayment
{ {
Name = model.Name, Name = model.Name,
Description = model.Description, Description = model.Description,
@@ -153,17 +151,15 @@ namespace BTCPayServer.Controllers
Currency = model.Currency, Currency = model.Currency,
StoreId = storeId, StoreId = storeId,
PayoutMethodIds = selectedPaymentMethodIds, PayoutMethodIds = selectedPaymentMethodIds,
EmbeddedCSS = model.EmbeddedCSS,
CustomCSSLink = model.CustomCSSLink,
BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration), BOLT11Expiration = TimeSpan.FromDays(model.BOLT11Expiration),
AutoApproveClaims = model.AutoApproveClaims AutoApproveClaims = model.AutoApproveClaims
}); });
this.TempData.SetStatusMessageModel(new StatusMessageModel() TempData.SetStatusMessageModel(new StatusMessageModel
{ {
Message = "Pull payment request created", Message = "Pull payment request created",
Severity = StatusMessageModel.StatusSeverity.Success Severity = StatusMessageModel.StatusSeverity.Success
}); });
return RedirectToAction(nameof(PullPayments), new { storeId = storeId }); return RedirectToAction(nameof(PullPayments), new { storeId });
} }
[Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Data; using BTCPayServer.Data;
@@ -19,20 +20,19 @@ namespace BTCPayServer.Controllers;
public partial class UIStoresController public partial class UIStoresController
{ {
[HttpGet("{storeId}/settings")] [HttpGet("{storeId}/settings")]
public IActionResult GeneralSettings() public async Task<IActionResult> GeneralSettings()
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
return NotFound(); return NotFound();
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var vm = new GeneralSettingsViewModel var vm = new GeneralSettingsViewModel
{ {
Id = store.Id, Id = store.Id,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreWebsite = store.StoreWebsite, StoreWebsite = store.StoreWebsite,
LogoFileId = storeBlob.LogoFileId, LogoUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.LogoUrl),
CssFileId = storeBlob.CssFileId, CssUrl = await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.CssUrl),
BrandColor = storeBlob.BrandColor, BrandColor = storeBlob.BrandColor,
NetworkFeeMode = storeBlob.NetworkFeeMode, NetworkFeeMode = storeBlob.NetworkFeeMode,
AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice, AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice,
@@ -104,16 +104,11 @@ public partial class UIStoresController
else else
{ {
model.LogoFile = formFile; model.LogoFile = formFile;
// delete existing file
if (!string.IsNullOrEmpty(blob.LogoFileId))
{
await _fileService.RemoveFile(blob.LogoFileId, userId);
}
// add new image // add new image
try try
{ {
var storedFile = await _fileService.AddFile(model.LogoFile, userId); var storedFile = await _fileService.AddFile(model.LogoFile, userId);
blob.LogoFileId = storedFile.Id; blob.LogoUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -122,10 +117,9 @@ public partial class UIStoresController
} }
} }
} }
else if (RemoveLogoFile && !string.IsNullOrEmpty(blob.LogoFileId)) else if (RemoveLogoFile && blob.LogoUrl is not null)
{ {
await _fileService.RemoveFile(blob.LogoFileId, userId); blob.LogoUrl = null;
blob.LogoFileId = null;
needUpdate = true; needUpdate = true;
} }
@@ -145,16 +139,11 @@ public partial class UIStoresController
} }
else else
{ {
// delete existing file
if (!string.IsNullOrEmpty(blob.CssFileId))
{
await _fileService.RemoveFile(blob.CssFileId, userId);
}
// add new file // add new file
try try
{ {
var storedFile = await _fileService.AddFile(model.CssFile, userId); var storedFile = await _fileService.AddFile(model.CssFile, userId);
blob.CssFileId = storedFile.Id; blob.CssUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -162,10 +151,9 @@ public partial class UIStoresController
} }
} }
} }
else if (RemoveCssFile && !string.IsNullOrEmpty(blob.CssFileId)) else if (RemoveCssFile && blob.CssUrl is not null)
{ {
await _fileService.RemoveFile(blob.CssFileId, userId); blob.CssUrl = null;
blob.CssFileId = null;
needUpdate = true; needUpdate = true;
} }
@@ -221,7 +209,7 @@ public partial class UIStoresController
} }
[HttpGet("{storeId}/checkout")] [HttpGet("{storeId}/checkout")]
public IActionResult CheckoutAppearance() public async Task<IActionResult> CheckoutAppearance()
{ {
var storeBlob = CurrentStore.GetStoreBlob(); var storeBlob = CurrentStore.GetStoreBlob();
var vm = new CheckoutAppearanceViewModel(); var vm = new CheckoutAppearanceViewModel();
@@ -253,7 +241,9 @@ public partial class UIStoresController
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi; vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
vm.LazyPaymentMethods = storeBlob.LazyPaymentMethods; vm.LazyPaymentMethods = storeBlob.LazyPaymentMethods;
vm.RedirectAutomatically = storeBlob.RedirectAutomatically; vm.RedirectAutomatically = storeBlob.RedirectAutomatically;
vm.SoundFileId = storeBlob.SoundFileId; vm.PaymentSoundUrl = storeBlob.PaymentSoundUrl is null
? string.Concat(Request.GetAbsoluteRootUri().ToString(), "checkout/payment.mp3")
: await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.PaymentSoundUrl);
vm.HtmlTitle = storeBlob.HtmlTitle; vm.HtmlTitle = storeBlob.HtmlTitle;
vm.SupportUrl = storeBlob.StoreSupportUrl; vm.SupportUrl = storeBlob.StoreSupportUrl;
vm.DisplayExpirationTimer = (int)storeBlob.DisplayExpirationTimer.TotalMinutes; vm.DisplayExpirationTimer = (int)storeBlob.DisplayExpirationTimer.TotalMinutes;
@@ -316,17 +306,11 @@ public partial class UIStoresController
else else
{ {
model.SoundFile = formFile; model.SoundFile = formFile;
// delete existing file
if (!string.IsNullOrEmpty(blob.SoundFileId))
{
await _fileService.RemoveFile(blob.SoundFileId, userId);
}
// add new file // add new file
try try
{ {
var storedFile = await _fileService.AddFile(model.SoundFile, userId); var storedFile = await _fileService.AddFile(model.SoundFile, userId);
blob.SoundFileId = storedFile.Id; blob.PaymentSoundUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
needUpdate = true; needUpdate = true;
} }
catch (Exception e) catch (Exception e)
@@ -336,10 +320,9 @@ public partial class UIStoresController
} }
} }
} }
else if (RemoveSoundFile && !string.IsNullOrEmpty(blob.SoundFileId)) else if (RemoveSoundFile && blob.PaymentSoundUrl is not null)
{ {
await _fileService.RemoveFile(blob.SoundFileId, userId); blob.PaymentSoundUrl = null;
blob.SoundFileId = null;
needUpdate = true; needUpdate = true;
} }

View File

@@ -56,6 +56,7 @@ public partial class UIStoresController : Controller
IHtmlHelper html, IHtmlHelper html,
EmailSenderFactory emailSenderFactory, EmailSenderFactory emailSenderFactory,
WalletFileParsers onChainWalletParsers, WalletFileParsers onChainWalletParsers,
UriResolver uriResolver,
SettingsRepository settingsRepository, SettingsRepository settingsRepository,
EventAggregator eventAggregator) EventAggregator eventAggregator)
{ {
@@ -78,6 +79,7 @@ public partial class UIStoresController : Controller
_externalServiceOptions = externalServiceOptions; _externalServiceOptions = externalServiceOptions;
_emailSenderFactory = emailSenderFactory; _emailSenderFactory = emailSenderFactory;
_onChainWalletParsers = onChainWalletParsers; _onChainWalletParsers = onChainWalletParsers;
_uriResolver = uriResolver;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_html = html; _html = html;
@@ -106,6 +108,7 @@ public partial class UIStoresController : Controller
private readonly IOptions<ExternalServicesOptions> _externalServiceOptions; private readonly IOptions<ExternalServicesOptions> _externalServiceOptions;
private readonly EmailSenderFactory _emailSenderFactory; private readonly EmailSenderFactory _emailSenderFactory;
private readonly WalletFileParsers _onChainWalletParsers; private readonly WalletFileParsers _onChainWalletParsers;
private readonly UriResolver _uriResolver;
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly IHtmlHelper _html; private readonly IHtmlHelper _html;
private readonly WebhookSender _webhookNotificationManager; private readonly WebhookSender _webhookNotificationManager;

View File

@@ -35,9 +35,7 @@ namespace BTCPayServer.Data
{ {
public string Title { get; set; } public string Title { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string EmbeddedCSS { get; set; }
public string Email { get; set; } public string Email { get; set; }
public string CustomCSSLink { get; set; }
} }
} }
} }

View File

@@ -11,8 +11,6 @@ using BTCPayServer.JsonConverters;
using BTCPayServer.Payments; using BTCPayServer.Payments;
using BTCPayServer.Rating; using BTCPayServer.Rating;
using BTCPayServer.Services.Mails; using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -217,8 +215,10 @@ namespace BTCPayServer.Data
public List<UIStoresController.StoreEmailRule> EmailRules { get; set; } public List<UIStoresController.StoreEmailRule> EmailRules { get; set; }
public string BrandColor { get; set; } public string BrandColor { get; set; }
public string LogoFileId { get; set; } [JsonConverter(typeof(UnresolvedUriJsonConverter))]
public string CssFileId { get; set; } public UnresolvedUri LogoUrl { get; set; }
[JsonConverter(typeof(UnresolvedUriJsonConverter))]
public UnresolvedUri CssUrl { get; set; }
[DefaultValue(true)] [DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
@@ -236,7 +236,8 @@ namespace BTCPayServer.Data
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool PlaySoundOnPayment { get; set; } public bool PlaySoundOnPayment { get; set; }
public string SoundFileId { get; set; } [JsonConverter(typeof(UnresolvedUriJsonConverter))]
public UnresolvedUri PaymentSoundUrl { get; set; }
public IPaymentFilter GetExcludedPaymentMethods() public IPaymentFilter GetExcludedPaymentMethods()
{ {

View File

@@ -14,6 +14,7 @@ using BTCPayServer.Data;
using BTCPayServer.Filters; using BTCPayServer.Filters;
using BTCPayServer.Forms.Models; using BTCPayServer.Forms.Models;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -26,15 +27,18 @@ namespace BTCPayServer.Forms;
public class UIFormsController : Controller public class UIFormsController : Controller
{ {
private readonly FormDataService _formDataService; private readonly FormDataService _formDataService;
private readonly UriResolver _uriResolver;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private FormComponentProviders FormProviders { get; } private FormComponentProviders FormProviders { get; }
public UIFormsController(FormComponentProviders formProviders, FormDataService formDataService, public UIFormsController(FormComponentProviders formProviders, FormDataService formDataService,
UriResolver uriResolver,
StoreRepository storeRepository, IAuthorizationService authorizationService) StoreRepository storeRepository, IAuthorizationService authorizationService)
{ {
FormProviders = formProviders; FormProviders = formProviders;
_formDataService = formDataService; _formDataService = formDataService;
_uriResolver = uriResolver;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_storeRepository = storeRepository; _storeRepository = storeRepository;
} }
@@ -169,7 +173,7 @@ public class UIFormsController : Controller
FormName = formData.Name, FormName = formData.Name,
Form = form, Form = form,
StoreName = store?.StoreName, StoreName = store?.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob) StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob)
}); });
} }

View File

@@ -38,8 +38,6 @@ namespace BTCPayServer.HostedServices
public string Description { get; set; } public string Description { get; set; }
public decimal Amount { get; set; } public decimal Amount { get; set; }
public string Currency { get; set; } public string Currency { get; set; }
public string CustomCSSLink { get; set; }
public string EmbeddedCSS { get; set; }
public PayoutMethodId[] PayoutMethodIds { get; set; } public PayoutMethodId[] PayoutMethodIds { get; set; }
public bool AutoApproveClaims { get; set; } public bool AutoApproveClaims { get; set; }
public TimeSpan? BOLT11Expiration { get; set; } public TimeSpan? BOLT11Expiration { get; set; }
@@ -131,13 +129,11 @@ namespace BTCPayServer.HostedServices
Limit = create.Amount, Limit = create.Amount,
SupportedPaymentMethods = create.PayoutMethodIds, SupportedPaymentMethods = create.PayoutMethodIds,
AutoApproveClaims = create.AutoApproveClaims, AutoApproveClaims = create.AutoApproveClaims,
View = new PullPaymentBlob.PullPaymentView() View = new PullPaymentBlob.PullPaymentView
{ {
Title = create.Name ?? string.Empty, Title = create.Name ?? string.Empty,
Description = create.Description ?? string.Empty, Description = create.Description ?? string.Empty,
CustomCSSLink = create.CustomCSSLink, Email = null
Email = null,
EmbeddedCSS = create.EmbeddedCSS,
}, },
BOLT11Expiration = create.BOLT11Expiration ?? TimeSpan.FromDays(30.0) BOLT11Expiration = create.BOLT11Expiration ?? TimeSpan.FromDays(30.0)
}); });

View File

@@ -168,6 +168,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<EventAggregator>(); services.TryAddSingleton<EventAggregator>();
services.TryAddSingleton<PaymentRequestService>(); services.TryAddSingleton<PaymentRequestService>();
services.TryAddSingleton<UserService>(); services.TryAddSingleton<UserService>();
services.TryAddSingleton<UriResolver>();
services.TryAddSingleton<WalletHistogramService>(); services.TryAddSingleton<WalletHistogramService>();
services.AddSingleton<ApplicationDbContextFactory>(); services.AddSingleton<ApplicationDbContextFactory>();
services.AddOptions<BTCPayServerOptions>().Configure( services.AddOptions<BTCPayServerOptions>().Configure(

View File

@@ -4,9 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Common;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Fido2; using BTCPayServer.Fido2;
@@ -26,17 +24,14 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Storage.Models; using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration; using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
using Fido2NetLib.Objects; using Fido2NetLib.Objects;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NBitcoin;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PeterO.Cbor; using PeterO.Cbor;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
using LightningAddressData = BTCPayServer.Data.LightningAddressData; using LightningAddressData = BTCPayServer.Data.LightningAddressData;
using Serializer = NBXplorer.Serializer;
namespace BTCPayServer.Hosting namespace BTCPayServer.Hosting
{ {
@@ -53,6 +48,7 @@ namespace BTCPayServer.Hosting
private readonly LightningAddressService _lightningAddressService; private readonly LightningAddressService _lightningAddressService;
private readonly ILogger<MigrationStartupTask> _logger; private readonly ILogger<MigrationStartupTask> _logger;
private readonly LightningClientFactoryService _lightningClientFactoryService; private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IFileService _fileService;
public IOptions<LightningNetworkOptions> LightningOptions { get; } public IOptions<LightningNetworkOptions> LightningOptions { get; }
@@ -67,6 +63,7 @@ namespace BTCPayServer.Hosting
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
LightningAddressService lightningAddressService, LightningAddressService lightningAddressService,
ILogger<MigrationStartupTask> logger, ILogger<MigrationStartupTask> logger,
IFileService fileService,
LightningClientFactoryService lightningClientFactoryService) LightningClientFactoryService lightningClientFactoryService)
{ {
_handlers = handlers; _handlers = handlers;
@@ -78,6 +75,7 @@ namespace BTCPayServer.Hosting
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_lightningAddressService = lightningAddressService; _lightningAddressService = lightningAddressService;
_logger = logger; _logger = logger;
_fileService = fileService;
_lightningClientFactoryService = lightningClientFactoryService; _lightningClientFactoryService = lightningClientFactoryService;
LightningOptions = lightningOptions; LightningOptions = lightningOptions;
} }

View File

@@ -0,0 +1,29 @@
using System;
using System.Reflection;
using BTCPayServer.Payouts;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.JsonConverters
{
public class UnresolvedUriJsonConverter : JsonConverter<UnresolvedUri>
{
public override UnresolvedUri ReadJson(JsonReader reader, Type objectType, UnresolvedUri existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new JsonObjectException("A UnresolvedUri should be a string", reader);
var str = (string)reader.Value;
if (str.Length == 0)
return null;
return UnresolvedUri.Create(str);
}
public override void WriteJson(JsonWriter writer, UnresolvedUri value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(value.ToString());
}
}
}

View File

@@ -48,8 +48,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
Description = blob.Description; Description = blob.Description;
ExpiryDate = blob.ExpiryDate?.UtcDateTime; ExpiryDate = blob.ExpiryDate?.UtcDateTime;
Email = blob.Email; Email = blob.Email;
CustomCSSLink = blob.CustomCSSLink;
EmbeddedCSS = blob.EmbeddedCSS;
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts; AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts;
FormResponse = blob.FormResponse is null FormResponse = blob.FormResponse is null
? null ? null
@@ -86,13 +84,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
[MailboxAddress] [MailboxAddress]
public string Email { get; set; } public string Email { get; set; }
[MaxLength(500)]
[Display(Name = "Custom CSS URL")]
public string CustomCSSLink { get; set; }
[Display(Name = "Custom CSS Code")]
public string EmbeddedCSS { get; set; }
[Display(Name = "Allow payee to create invoices with custom amounts")] [Display(Name = "Allow payee to create invoices with custom amounts")]
public bool AllowCustomPaymentAmounts { get; set; } public bool AllowCustomPaymentAmounts { get; set; }
@@ -115,8 +106,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
Description = blob.Description; Description = blob.Description;
ExpiryDate = blob.ExpiryDate?.UtcDateTime; ExpiryDate = blob.ExpiryDate?.UtcDateTime;
Email = blob.Email; Email = blob.Email;
EmbeddedCSS = blob.EmbeddedCSS;
CustomCSSLink = blob.CustomCSSLink;
AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts; AllowCustomPaymentAmounts = blob.AllowCustomPaymentAmounts;
switch (data.Status) switch (data.Status)
{ {
@@ -154,8 +143,6 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public string Description { get; set; } public string Description { get; set; }
public string StoreName { get; set; } public string StoreName { get; set; }
public string StoreWebsite { get; set; } public string StoreWebsite { get; set; }
public string EmbeddedCSS { get; set; }
public string CustomCSSLink { get; set; }
#nullable enable #nullable enable
public class InvoiceList : List<PaymentRequestInvoice> public class InvoiceList : List<PaymentRequestInvoice>

View File

@@ -21,20 +21,15 @@ public class BrandingViewModel
[Display(Name = "Custom Theme Extension Type")] [Display(Name = "Custom Theme Extension Type")]
public ThemeExtension CustomThemeExtension { get; set; } public ThemeExtension CustomThemeExtension { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
[MaxLength(500)]
[Display(Name = "Custom Theme CSS URL")]
public string CustomThemeCssUri { get; set; }
[Display(Name = "Custom Theme File")] [Display(Name = "Custom Theme File")]
[JsonIgnore] [JsonIgnore]
public IFormFile CustomThemeFile { get; set; } public IFormFile CustomThemeFile { get; set; }
public string CustomThemeFileId { get; set; } public string CustomThemeCssUrl { get; set; }
[Display(Name = "Logo")] [Display(Name = "Logo")]
[JsonIgnore] [JsonIgnore]
public IFormFile LogoFile { get; set; } public IFormFile LogoFile { get; set; }
public string LogoFileId { get; set; } public string LogoUrl { get; set; }
} }

View File

@@ -1,24 +1,32 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Http;
namespace BTCPayServer.Models; namespace BTCPayServer.Models;
public class StoreBrandingViewModel public class StoreBrandingViewModel
{ {
public string BrandColor { get; set; } public string BrandColor { get; set; }
public string LogoFileId { get; set; } public string LogoUrl { get; set; }
public string CssFileId { get; set; } public string CssUrl { get; set; }
public string CustomCSSLink { get; set; }
public string EmbeddedCSS { get; set; }
public StoreBrandingViewModel() public StoreBrandingViewModel()
{ {
} }
public static async Task<StoreBrandingViewModel> CreateAsync(HttpRequest request, UriResolver uriResolver, StoreBlob storeBlob)
public StoreBrandingViewModel(StoreBlob storeBlob) {
if (storeBlob == null)
return new StoreBrandingViewModel();
var result = new StoreBrandingViewModel(storeBlob);
result.LogoUrl = await uriResolver.Resolve(request.GetAbsoluteRootUri(), storeBlob.LogoUrl);
result.CssUrl = await uriResolver.Resolve(request.GetAbsoluteRootUri(), storeBlob.CssUrl);
return result;
}
private StoreBrandingViewModel(StoreBlob storeBlob)
{ {
if (storeBlob == null) return;
BrandColor = storeBlob.BrandColor; BrandColor = storeBlob.BrandColor;
LogoFileId = storeBlob.LogoFileId;
CssFileId = storeBlob.CssFileId;
} }
} }

View File

@@ -59,7 +59,7 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Custom sound file for successful payment")] [Display(Name = "Custom sound file for successful payment")]
public IFormFile SoundFile { get; set; } public IFormFile SoundFile { get; set; }
public string SoundFileId { get; set; } public string PaymentSoundUrl { get; set; }
[Display(Name = "Custom HTML title to display on Checkout page")] [Display(Name = "Custom HTML title to display on Checkout page")]
public string HtmlTitle { get; set; } public string HtmlTitle { get; set; }

View File

@@ -27,11 +27,11 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Logo")] [Display(Name = "Logo")]
public IFormFile LogoFile { get; set; } public IFormFile LogoFile { get; set; }
public string LogoFileId { get; set; } public string LogoUrl { get; set; }
[Display(Name = "Custom CSS")] [Display(Name = "Custom CSS")]
public IFormFile CssFile { get; set; } public IFormFile CssFile { get; set; }
public string CssFileId { get; set; } public string CssUrl { get; set; }
public bool Archived { get; set; } public bool Archived { get; set; }

View File

@@ -55,11 +55,6 @@ namespace BTCPayServer.Models.WalletViewModels
[Required] [Required]
[ReadOnly(true)] [ReadOnly(true)]
public string Currency { get; set; } public string Currency { get; set; }
[MaxLength(500)]
[Display(Name = "Custom CSS URL")]
public string CustomCSSLink { get; set; }
[Display(Name = "Custom CSS Code")]
public string EmbeddedCSS { get; set; }
[Display(Name = "Payout Methods")] [Display(Name = "Payout Methods")]
public IEnumerable<string> PayoutMethods { get; set; } public IEnumerable<string> PayoutMethods { get; set; }
@@ -91,8 +86,6 @@ namespace BTCPayServer.Models.WalletViewModels
var blob = data.GetBlob(); var blob = data.GetBlob();
Name = blob.Name; Name = blob.Name;
Description = blob.Description; Description = blob.Description;
CustomCSSLink = blob.View.CustomCSSLink;
EmbeddedCSS = blob.View.EmbeddedCSS;
} }
[MaxLength(30)] [MaxLength(30)]
@@ -100,11 +93,5 @@ namespace BTCPayServer.Models.WalletViewModels
[Display(Name = "Memo")] [Display(Name = "Memo")]
public string Description { get; set; } public string Description { get; set; }
[Display(Name = "Custom CSS URL")]
public string CustomCSSLink { get; set; }
[Display(Name = "Custom CSS Code")]
public string EmbeddedCSS { get; set; }
} }
} }

View File

@@ -16,6 +16,7 @@ using BTCPayServer.Forms.Models;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Plugins.Crowdfund.Models; using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale.Models; using BTCPayServer.Plugins.PointOfSale.Models;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
@@ -41,6 +42,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
AppService appService, AppService appService,
CurrencyNameTable currencies, CurrencyNameTable currencies,
EventAggregator eventAggregator, EventAggregator eventAggregator,
UriResolver uriResolver,
StoreRepository storeRepository, StoreRepository storeRepository,
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
@@ -53,11 +55,13 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
_app = app; _app = app;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_uriResolver = uriResolver;
_invoiceController = invoiceController; _invoiceController = invoiceController;
FormDataService = formDataService; FormDataService = formDataService;
} }
private readonly EventAggregator _eventAggregator; private readonly EventAggregator _eventAggregator;
private readonly UriResolver _uriResolver;
private readonly CurrencyNameTable _currencies; private readonly CurrencyNameTable _currencies;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly AppService _appService; private readonly AppService _appService;
@@ -315,7 +319,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
var vm = new FormViewModel var vm = new FormViewModel
{ {
StoreName = store.StoreName, StoreName = store.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob), StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob),
FormName = formData.Name, FormName = formData.Name,
Form = form, Form = form,
AspController = controller, AspController = controller,
@@ -403,10 +407,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
TargetCurrency = settings.TargetCurrency, TargetCurrency = settings.TargetCurrency,
Description = settings.Description, Description = settings.Description,
MainImageUrl = settings.MainImageUrl, MainImageUrl = settings.MainImageUrl,
EmbeddedCSS = settings.EmbeddedCSS,
EndDate = settings.EndDate, EndDate = settings.EndDate,
TargetAmount = settings.TargetAmount, TargetAmount = settings.TargetAmount,
CustomCSSLink = settings.CustomCSSLink,
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
Tagline = settings.Tagline, Tagline = settings.Tagline,
PerksTemplate = settings.PerksTemplate, PerksTemplate = settings.PerksTemplate,
@@ -518,9 +520,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
Description = vm.Description, Description = vm.Description,
EndDate = vm.EndDate?.ToUniversalTime(), EndDate = vm.EndDate?.ToUniversalTime(),
TargetAmount = vm.TargetAmount, TargetAmount = vm.TargetAmount,
CustomCSSLink = vm.CustomCSSLink,
MainImageUrl = vm.MainImageUrl, MainImageUrl = vm.MainImageUrl,
EmbeddedCSS = vm.EmbeddedCSS,
NotificationUrl = vm.NotificationUrl, NotificationUrl = vm.NotificationUrl,
Tagline = vm.Tagline, Tagline = vm.Tagline,
PerksTemplate = vm.PerksTemplate, PerksTemplate = vm.PerksTemplate,
@@ -580,6 +580,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
return null; return null;
} }
var info = (ViewCrowdfundViewModel)await _app.GetInfo(app); var info = (ViewCrowdfundViewModel)await _app.GetInfo(app);
info.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob());
info.HubPath = AppHub.GetHubPath(Request); info.HubPath = AppHub.GetHubPath(Request);
info.SimpleDisplay = Request.Query.ContainsKey("simple"); info.SimpleDisplay = Request.Query.ContainsKey("simple");
return info; return info;

View File

@@ -18,6 +18,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using Ganss.Xss; using Ganss.Xss;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -180,11 +181,6 @@ namespace BTCPayServer.Plugins.Crowdfund
var store = appData.StoreData; var store = appData.StoreData;
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var storeBranding = new StoreBrandingViewModel(storeBlob)
{
CustomCSSLink = settings.CustomCSSLink,
EmbeddedCSS = settings.EmbeddedCSS
};
var formUrl = settings.FormId != null var formUrl = settings.FormId != null
? _linkGenerator.GetPathByAction(nameof(UICrowdfundController.CrowdfundForm), "UICrowdfund", ? _linkGenerator.GetPathByAction(nameof(UICrowdfundController.CrowdfundForm), "UICrowdfund",
new { appId = appData.Id }, _options.Value.RootPath) new { appId = appData.Id }, _options.Value.RootPath)
@@ -196,7 +192,6 @@ namespace BTCPayServer.Plugins.Crowdfund
Description = settings.Description, Description = settings.Description,
MainImageUrl = settings.MainImageUrl, MainImageUrl = settings.MainImageUrl,
StoreName = store.StoreName, StoreName = store.StoreName,
StoreBranding = storeBranding,
StoreId = appData.StoreDataId, StoreId = appData.StoreDataId,
AppId = appData.Id, AppId = appData.Id,
StartDate = settings.StartDate?.ToUniversalTime(), StartDate = settings.StartDate?.ToUniversalTime(),

View File

@@ -86,13 +86,6 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
[Display(Name = "Contribution Perks Template")] [Display(Name = "Contribution Perks Template")]
public string PerksTemplate { get; set; } public string PerksTemplate { get; set; }
[MaxLength(500)]
[Display(Name = "Custom CSS URL")]
public string CustomCSSLink { get; set; }
[Display(Name = "Custom CSS Code")]
public string EmbeddedCSS { get; set; }
[Display(Name = "Count all invoices created on the store as part of the goal")] [Display(Name = "Count all invoices created on the store as part of the goal")]
public bool UseAllStoreInvoices { get; set; } public bool UseAllStoreInvoices { get; set; }

View File

@@ -47,6 +47,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
AppService appService, AppService appService,
CurrencyNameTable currencies, CurrencyNameTable currencies,
StoreRepository storeRepository, StoreRepository storeRepository,
UriResolver uriResolver,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
UIInvoiceController invoiceController, UIInvoiceController invoiceController,
FormDataService formDataService, FormDataService formDataService,
@@ -55,6 +56,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
_currencies = currencies; _currencies = currencies;
_appService = appService; _appService = appService;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_uriResolver = uriResolver;
_invoiceRepository = invoiceRepository; _invoiceRepository = invoiceRepository;
_invoiceController = invoiceController; _invoiceController = invoiceController;
_displayFormatter = displayFormatter; _displayFormatter = displayFormatter;
@@ -64,6 +66,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
private readonly CurrencyNameTable _currencies; private readonly CurrencyNameTable _currencies;
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly UriResolver _uriResolver;
private readonly AppService _appService; private readonly AppService _appService;
private readonly UIInvoiceController _invoiceController; private readonly UIInvoiceController _invoiceController;
private readonly DisplayFormatter _displayFormatter; private readonly DisplayFormatter _displayFormatter;
@@ -86,13 +89,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView; viewType ??= settings.EnableShoppingCart ? PosViewType.Cart : settings.DefaultView;
var store = await _appService.GetStore(app); var store = await _appService.GetStore(app);
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
var storeBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
var storeBranding = new StoreBrandingViewModel(storeBlob)
{
EmbeddedCSS = settings.EmbeddedCSS,
CustomCSSLink = settings.CustomCSSLink
};
// Check if the currency is COP or ARS (exclude decimal places)
return View($"PointOfSale/Public/{viewType}", new ViewPointOfSaleViewModel return View($"PointOfSale/Public/{viewType}", new ViewPointOfSaleViewModel
{ {
@@ -466,7 +463,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
var vm = new FormViewModel var vm = new FormViewModel
{ {
StoreName = store.StoreName, StoreName = store.StoreName,
StoreBranding = new StoreBrandingViewModel(storeBlob), StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob),
FormName = formData.Name, FormName = formData.Name,
Form = form, Form = form,
AspController = controller, AspController = controller,
@@ -529,7 +526,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
viewModel.FormName = formData.Name; viewModel.FormName = formData.Name;
viewModel.Form = form; viewModel.Form = form;
viewModel.FormParameters = formParameters; viewModel.FormParameters = formParameters;
viewModel.StoreBranding = new StoreBrandingViewModel(storeBlob); viewModel.StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, storeBlob);
return View("Views/UIForms/View", viewModel); return View("Views/UIForms/View", viewModel);
} }
@@ -598,8 +595,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF, CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF, CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
CustomTipPercentages = settings.CustomTipPercentages != null ? string.Join(",", settings.CustomTipPercentages) : string.Join(",", PointOfSaleSettings.CUSTOM_TIP_PERCENTAGES_DEF), CustomTipPercentages = settings.CustomTipPercentages != null ? string.Join(",", settings.CustomTipPercentages) : string.Join(",", PointOfSaleSettings.CUSTOM_TIP_PERCENTAGES_DEF),
CustomCSSLink = settings.CustomCSSLink,
EmbeddedCSS = settings.EmbeddedCSS,
Description = settings.Description, Description = settings.Description,
NotificationUrl = settings.NotificationUrl, NotificationUrl = settings.NotificationUrl,
RedirectUrl = settings.RedirectUrl, RedirectUrl = settings.RedirectUrl,
@@ -689,11 +684,9 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
CustomButtonText = vm.CustomButtonText, CustomButtonText = vm.CustomButtonText,
CustomTipText = vm.CustomTipText, CustomTipText = vm.CustomTipText,
CustomTipPercentages = ListSplit(vm.CustomTipPercentages), CustomTipPercentages = ListSplit(vm.CustomTipPercentages),
CustomCSSLink = vm.CustomCSSLink,
NotificationUrl = vm.NotificationUrl, NotificationUrl = vm.NotificationUrl,
RedirectUrl = vm.RedirectUrl, RedirectUrl = vm.RedirectUrl,
Description = vm.Description, Description = vm.Description,
EmbeddedCSS = vm.EmbeddedCSS,
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically), RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically),
FormId = vm.FormId FormId = vm.FormId
}; };

View File

@@ -67,10 +67,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
[Display(Name = "Tip percentage amounts (comma separated)")] [Display(Name = "Tip percentage amounts (comma separated)")]
public string CustomTipPercentages { get; set; } public string CustomTipPercentages { get; set; }
[MaxLength(500)]
[Display(Name = "Custom CSS URL")]
public string CustomCSSLink { get; set; }
public string Id { get; set; } public string Id { get; set; }
[Display(Name = "Redirect invoice to redirect url automatically after paid")] [Display(Name = "Redirect invoice to redirect url automatically after paid")]
@@ -99,8 +95,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
} }
}, nameof(SelectListItem.Value), nameof(SelectListItem.Text), RedirectAutomatically); }, nameof(SelectListItem.Value), nameof(SelectListItem.Text), RedirectAutomatically);
[Display(Name = "Custom CSS Code")]
public string EmbeddedCSS { get; set; }
public string Description { get; set; } public string Description { get; set; }
[Display(Name = "Request customer data on checkout")] [Display(Name = "Request customer data on checkout")]

View File

@@ -17,6 +17,7 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Dapper; using Dapper;
using Ganss.Xss; using Ganss.Xss;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;

View File

@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Http;
namespace BTCPayServer.Services.Apps namespace BTCPayServer.Services.Apps
{ {

View File

@@ -28,11 +28,9 @@ namespace BTCPayServer.Services.Apps
} }
public bool EnforceTargetAmount { get; set; } public bool EnforceTargetAmount { get; set; }
public string CustomCSSLink { get; set; }
public string MainImageUrl { get; set; } public string MainImageUrl { get; set; }
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }
public string Tagline { get; set; } public string Tagline { get; set; }
public string EmbeddedCSS { get; set; }
public string PerksTemplate { get; set; } public string PerksTemplate { get; set; }
public bool DisqusEnabled { get; set; } public bool DisqusEnabled { get; set; }
public bool SoundsEnabled { get; set; } public bool SoundsEnabled { get; set; }

View File

@@ -103,11 +103,6 @@ namespace BTCPayServer.Services.Apps
public string CustomTipText { get; set; } = CUSTOM_TIP_TEXT_DEF; public string CustomTipText { get; set; } = CUSTOM_TIP_TEXT_DEF;
public static readonly int[] CUSTOM_TIP_PERCENTAGES_DEF = new int[] { 15, 18, 20 }; public static readonly int[] CUSTOM_TIP_PERCENTAGES_DEF = new int[] { 15, 18, 20 };
public int[] CustomTipPercentages { get; set; } = CUSTOM_TIP_PERCENTAGES_DEF; public int[] CustomTipPercentages { get; set; } = CUSTOM_TIP_PERCENTAGES_DEF;
public string CustomCSSLink { get; set; }
public string EmbeddedCSS { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string NotificationUrl { get; set; } public string NotificationUrl { get; set; }
public string RedirectUrl { get; set; } public string RedirectUrl { get; set; }

View File

@@ -1,4 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace BTCPayServer.Services; namespace BTCPayServer.Services;
@@ -21,14 +23,11 @@ public class ThemeSettings
[Display(Name = "Custom Theme Extension Type")] [Display(Name = "Custom Theme Extension Type")]
public ThemeExtension CustomThemeExtension { get; set; } public ThemeExtension CustomThemeExtension { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] [JsonConverter(typeof(UnresolvedUriJsonConverter))]
[MaxLength(500)] public UnresolvedUri CustomThemeCssUrl { get; set; }
[Display(Name = "Custom Theme CSS URL")]
public string CustomThemeCssUri { get; set; }
public string CustomThemeFileId { get; set; } [JsonConverter(typeof(UnresolvedUriJsonConverter))]
public UnresolvedUri LogoUrl { get; set; }
public string LogoFileId { get; set; }
public bool FirstRun { get; set; } = true; public bool FirstRun { get; set; } = true;
@@ -37,9 +36,4 @@ public class ThemeSettings
// no logs // no logs
return string.Empty; return string.Empty;
} }
public string CssUri
{
get => CustomTheme ? CustomThemeCssUri : "/main/themes/default.css";
}
} }

View File

@@ -0,0 +1,37 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.AccessControl;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Services
{
public class UriResolver
{
private readonly IFileService _fileService;
public UriResolver(IFileService fileService)
{
_fileService = fileService;
}
/// <summary>
/// If <paramref name="url"/> is an absolute URL, returns it as is.
/// If <paramref name="url"/> starts with "fileid:ID", returns the URL of the file with the ID.
/// </summary>
/// <param name="baseUri"><see cref="BTCPayServer.Abstractions.Extensions.HttpRequestExtensions.GetAbsoluteRootUri"/></param>
/// <param name="uri"></param>
/// <returns></returns>
public async Task<string?> Resolve(Uri baseUri, UnresolvedUri? uri)
{
return uri switch
{
null => null,
UnresolvedUri.FileIdUri fileId => await _fileService.GetFileUrl(baseUri, fileId.FileId),
UnresolvedUri.Raw raw => raw.Uri,
_ => throw new NotSupportedException(uri.GetType().Name)
};
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace BTCPayServer
{
public record UnresolvedUri
{
public static UnresolvedUri Create(string str)
{
ArgumentNullException.ThrowIfNull(str);
if (str.StartsWith("fileid:", StringComparison.OrdinalIgnoreCase))
{
return new FileIdUri(str.Substring("fileid:".Length));
}
return new Raw(str);
}
public record FileIdUri(string FileId) : UnresolvedUri
{
public override string ToString() => $"fileid:{FileId}";
}
public record Raw(string Uri) : UnresolvedUri
{
public override string ToString() => Uri;
}
}
}

View File

@@ -311,36 +311,6 @@
</div> </div>
</div> </div>
</div> </div>
@* We are deprecating the custom CSS options in favor of the store branding approach.
Display this section only if these values are set. *@
@if (!string.IsNullOrWhiteSpace(Model.CustomCSSLink) || !string.IsNullOrWhiteSpace(Model.EmbeddedCSS))
{
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" />
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group mb-4">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,14 +1,4 @@
@model StoreBrandingViewModel @model StoreBrandingViewModel
@using BTCPayServer.Abstractions.Contracts
@inject IFileService FileService
@{
var logoUrl = !string.IsNullOrEmpty(Model.LogoFileId)
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.LogoFileId)
: null;
var cssUrl = !string.IsNullOrEmpty(Model.CssFileId)
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.CssFileId)
: null;
}
@if (!string.IsNullOrEmpty(Model.BrandColor)) @if (!string.IsNullOrEmpty(Model.BrandColor))
{ {
var brand = Model.BrandColor; var brand = Model.BrandColor;
@@ -39,23 +29,12 @@
</style> </style>
<meta name="theme-color" content="@brand"> <meta name="theme-color" content="@brand">
} }
@if (!string.IsNullOrEmpty(cssUrl)) @if (!string.IsNullOrEmpty(Model.CssUrl))
{ {
<link href="@cssUrl" asp-append-version="true" rel="stylesheet" /> <link href="@Model.CssUrl!" asp-append-version="true" rel="stylesheet" />
} }
@* Deprecated, but added for backwards-compatibility *@ @if (!string.IsNullOrEmpty(Model.LogoUrl))
@if (!string.IsNullOrEmpty(Model.CustomCSSLink))
{ {
<link href="@Model.CustomCSSLink" asp-append-version="true" rel="stylesheet" /> <link rel="icon" href="@Model.LogoUrl">
} <link rel="apple-touch-icon" href="@Model.LogoUrl">
@if (!string.IsNullOrEmpty(Model.EmbeddedCSS))
{
<style>
@Safe.Raw(Model.EmbeddedCSS)
</style>
}
@if (!string.IsNullOrEmpty(logoUrl))
{
<link rel="icon" href="@logoUrl">
<link rel="apple-touch-icon" href="@logoUrl">
} }

View File

@@ -1,14 +1,9 @@
@using BTCPayServer.Services @using BTCPayServer.Services
@using BTCPayServer.Abstractions.Contracts
@inject ThemeSettings Theme @inject ThemeSettings Theme
@inject IFileService FileService @inject UriResolver UriResolver
<script>if (window.localStorage.getItem('btcpay-hide-sensitive-info') === 'true') { document.documentElement.setAttribute('data-hide-sensitive-info', 'true')}</script> <script>if (window.localStorage.getItem('btcpay-hide-sensitive-info') === 'true') { document.documentElement.setAttribute('data-hide-sensitive-info', 'true')}</script>
@if (Theme.CustomTheme && !string.IsNullOrEmpty(Theme.CssUri)) @if (Theme.CustomTheme && Theme.CustomThemeCssUrl is not null)
{ // legacy customization with CSS URI - keep it for backwards-compatibility
<link href="@Context.Request.GetRelativePathOrAbsolute(Theme.CssUri)" rel="stylesheet" asp-append-version="true" />
}
else if (Theme.CustomTheme && !string.IsNullOrEmpty(Theme.CustomThemeFileId))
{ // new customization uses theme file id provided by upload { // new customization uses theme file id provided by upload
@if (Theme.CustomThemeExtension != ThemeExtension.Custom) @if (Theme.CustomThemeExtension != ThemeExtension.Custom)
{ // needs to be added for light and dark, because dark extends light { // needs to be added for light and dark, because dark extends light
@@ -17,8 +12,9 @@ else if (Theme.CustomTheme && !string.IsNullOrEmpty(Theme.CustomThemeFileId))
@if (Theme.CustomThemeExtension == ThemeExtension.Dark) @if (Theme.CustomThemeExtension == ThemeExtension.Dark)
{ {
<link href="~/main/themes/default-dark.css" rel="stylesheet" asp-append-version="true" /> <link href="~/main/themes/default-dark.css" rel="stylesheet" asp-append-version="true" />
} }
<link href="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Theme.CustomThemeFileId))" rel="stylesheet" asp-append-version="true" /> var themeUrl = await UriResolver.Resolve(this.Context.Request.GetAbsoluteRootUri(), Theme.CustomThemeCssUrl);
<link href="@themeUrl" rel="stylesheet" asp-append-version="true" />
} }
else else
{ {

View File

@@ -289,36 +289,6 @@
</div> </div>
</div> </div>
</div> </div>
@* We are deprecating the custom CSS options in favor of the store branding approach.
Display this section only if these values are set. *@
@if (!string.IsNullOrWhiteSpace(Model.CustomCSSLink) || !string.IsNullOrWhiteSpace(Model.EmbeddedCSS))
{
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" />
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,15 +1,9 @@
@inject IFileService FileService
@using BTCPayServer.Abstractions.Contracts
@model (string Title, StoreBrandingViewModel StoreBranding) @model (string Title, StoreBrandingViewModel StoreBranding)
@{
var logoUrl = !string.IsNullOrEmpty(Model.StoreBranding.LogoFileId)
? await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.StoreBranding.LogoFileId)
: null;
}
<header class="store-header" v-pre> <header class="store-header" v-pre>
@if (!string.IsNullOrEmpty(logoUrl)) @if (!string.IsNullOrEmpty(Model.StoreBranding.LogoUrl))
{ {
<img src="@logoUrl" alt="@Model.Title" class="store-logo"/> <img src="@Model.StoreBranding.LogoUrl" alt="@Model.Title" class="store-logo"/>
} }
<h1 class="store-name">@Model.Title</h1> <h1 class="store-name">@Model.Title</h1>
</header> </header>

View File

@@ -15,7 +15,7 @@
<body class="min-vh-100"> <body class="min-vh-100">
<div id="FormView" class="public-page-wrap"> <div id="FormView" class="public-page-wrap">
<partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData) { { "Margin", "mb-4" } })" /> <partial name="_StatusMessage" model="@(new ViewDataDictionary(ViewData) { { "Margin", "mb-4" } })" />
@if (!string.IsNullOrEmpty(Model.StoreName) || !string.IsNullOrEmpty(Model.StoreBranding.LogoFileId)) @if (!string.IsNullOrEmpty(Model.StoreName) || !string.IsNullOrEmpty(Model.StoreBranding.LogoUrl))
{ {
<partial name="_StoreHeader" model="(Model.StoreName, Model.StoreBranding)" /> <partial name="_StoreHeader" model="(Model.StoreName, Model.StoreBranding)" />
} }

View File

@@ -123,46 +123,6 @@
</div> </div>
</div> </div>
</div> </div>
@* We are deprecating the custom CSS options in favor of the store branding approach.
Display this section only if these values are set. *@
@if (!string.IsNullOrWhiteSpace(Model.CustomCSSLink) || !string.IsNullOrWhiteSpace(Model.EmbeddedCSS))
{
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<h4 class="mt-5 mb-2">Additional Options</h4>
<div class="form-group">
<div class="accordion" id="additional">
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" />
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
</form> </form>
@if (!string.IsNullOrEmpty(Model.Id)) @if (!string.IsNullOrEmpty(Model.Id))

View File

@@ -1,5 +1,4 @@
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Models.WalletViewModels.UpdatePullPaymentModel @model BTCPayServer.Models.WalletViewModels.UpdatePullPaymentModel
@{ @{
@@ -53,44 +52,4 @@
</div> </div>
</div> </div>
</div> </div>
@* We are deprecating the custom CSS options in favor of the store branding approach.
Display this section only if these values are set. *@
@if (!string.IsNullOrWhiteSpace(Model.CustomCSSLink) || !string.IsNullOrWhiteSpace(Model.EmbeddedCSS))
{
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
<h4 class="mt-5 mb-2">Additional Options</h4>
<div class="form-group">
<div class="accordion" id="additional">
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/" target="_blank" rel="noreferrer noopener">
<vc:icon symbol="info" />
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
</form> </form>

View File

@@ -36,7 +36,7 @@
<div class="form-group"> <div class="form-group">
<div class="d-flex align-items-center justify-content-between gap-2"> <div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="LogoFile" class="form-label"></label> <label asp-for="LogoFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.LogoFileId)) @if (!string.IsNullOrEmpty(Model.LogoUrl))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true"> <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true">
<span class="fa fa-times"></span> Remove <span class="fa fa-times"></span> Remove
@@ -47,9 +47,9 @@
{ {
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<input asp-for="LogoFile" type="file" class="form-control flex-grow"> <input asp-for="LogoFile" type="file" class="form-control flex-grow">
@if (!string.IsNullOrEmpty(Model.LogoFileId)) @if (!string.IsNullOrEmpty(Model.LogoUrl))
{ {
<img src="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.LogoFileId))" alt="Logo" style="height:2.1rem;max-width:10.5rem;"/> <img src="@Model.LogoUrl" alt="Logo" style="height:2.1rem;max-width:10.5rem;"/>
} }
</div> </div>
<span asp-validation-for="LogoFile" class="text-danger"></span> <span asp-validation-for="LogoFile" class="text-danger"></span>
@@ -75,48 +75,37 @@
</div> </div>
</div> </div>
<div class="collapse @(Model.CustomTheme ? "show" : "")" id="CustomThemeSettings"> <div class="collapse @(Model.CustomTheme ? "show" : "")" id="CustomThemeSettings">
@if (!string.IsNullOrEmpty(Model.CustomThemeCssUri)) <div class="form-group pt-2">
{ <label asp-for="CustomThemeExtension" class="form-label" data-required></label>
<div class="form-group mb-0 pt-2"> <select asp-for="CustomThemeExtension" asp-items="@themeExtension" class="form-select w-auto"></select>
<label asp-for="CustomThemeCssUri" class="form-label" data-required></label> </div>
<input asp-for="CustomThemeCssUri" class="form-control"/> <div class="form-group mb-0">
<span asp-validation-for="CustomThemeCssUri" class="text-danger"></span> <div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="CustomThemeFile" class="form-label" data-required></label>
@if (!string.IsNullOrEmpty(Model.CustomThemeCssUrl))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveCustomThemeFile" value="true">
<span class="fa fa-times"></span> Remove
</button>
}
</div> </div>
} @if (canUpload)
else {
{ <div class="d-flex align-items-center gap-3">
<div class="form-group pt-2"> <input asp-for="CustomThemeFile" type="file" class="form-control flex-grow">
<label asp-for="CustomThemeExtension" class="form-label" data-required></label> @if (!string.IsNullOrEmpty(Model.CustomThemeCssUrl))
<select asp-for="CustomThemeExtension" asp-items="@themeExtension" class="form-select w-auto"></select>
</div>
<div class="form-group mb-0">
<div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="CustomThemeFile" class="form-label" data-required></label>
@if (!string.IsNullOrEmpty(Model.CustomThemeFileId))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveCustomThemeFile" value="true"> <a href="@Model.CustomThemeCssUrl" target="_blank" rel="noreferrer noopener" class="text-nowrap">Custom CSS</a>
<span class="fa fa-times"></span> Remove
</button>
} }
</div> </div>
@if (canUpload) <span asp-validation-for="CustomThemeFile" class="text-danger"></span>
{ }
<div class="d-flex align-items-center gap-3"> else
<input asp-for="CustomThemeFile" type="file" class="form-control flex-grow"> {
@if (!string.IsNullOrEmpty(Model.CustomThemeFileId)) <input asp-for="CustomThemeFile" type="file" class="form-control" disabled>
{ <p class="form-text text-muted">In order to upload a theme file, a <a asp-controller="UIServer" asp-action="Files">file storage</a> must be configured.</p>
<a href="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.CustomThemeFileId))" target="_blank" rel="noreferrer noopener" class="text-nowrap">Custom CSS</a> }
} </div>
</div>
<span asp-validation-for="CustomThemeFile" class="text-danger"></span>
}
else
{
<input asp-for="CustomThemeFile" type="file" class="form-control" disabled>
<p class="form-text text-muted">In order to upload a theme file, a <a asp-controller="UIServer" asp-action="Files">file storage</a> must be configured.</p>
}
</div>
}
</div> </div>
<button type="submit" class="btn btn-primary mt-2" name="command" value="Save">Save</button> <button type="submit" class="btn btn-primary mt-2" name="command" value="Save">Save</button>

View File

@@ -95,36 +95,6 @@
</div> </div>
</div> </div>
</div> </div>
@* We are deprecating the custom CSS options in favor of the store branding approach.
Display this section only if these values are set. *@
@if (!string.IsNullOrWhiteSpace(Model.CustomCSSLink) || !string.IsNullOrWhiteSpace(Model.EmbeddedCSS))
{
<div class="accordion-item">
<h2 class="accordion-header" id="additional-custom-css-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-custom-css" aria-expanded="false" aria-controls="additional-custom-css">
Custom CSS
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-custom-css" class="accordion-collapse collapse" aria-labelledby="additional-custom-css-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="CustomCSSLink" class="form-label"></label>
<a href="https://docs.btcpayserver.org/Development/Theme/" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info" />
</a>
<input asp-for="CustomCSSLink" class="form-control" />
<span asp-validation-for="CustomCSSLink" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EmbeddedCSS" class="form-label"></label>
<textarea asp-for="EmbeddedCSS" rows="10" cols="40" class="form-control"></textarea>
<span asp-validation-for="EmbeddedCSS" class="text-danger"></span>
</div>
</div>
</div>
</div>
}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -143,7 +143,7 @@
<div class="form-group mb-0 py-3"> <div class="form-group mb-0 py-3">
<div class="d-flex align-items-center justify-content-between gap-2"> <div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="SoundFile" class="form-label"></label> <label asp-for="SoundFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.SoundFileId)) @if (!string.IsNullOrEmpty(Model.PaymentSoundUrl))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true" permission="@Policies.CanModifyStoreSettings"> <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true" permission="@Policies.CanModifyStoreSettings">
<span class="fa fa-times"></span> Remove <span class="fa fa-times"></span> Remove
@@ -154,12 +154,7 @@
{ {
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<input asp-for="SoundFile" type="file" class="form-control flex-grow"> <input asp-for="SoundFile" type="file" class="form-control flex-grow">
@{ <audio controls src="@Model.PaymentSoundUrl" style="height:2.1rem;max-width:10.5rem;"></audio>
var soundUrl = string.IsNullOrEmpty(Model.SoundFileId)
? string.Concat(Context.Request.GetAbsoluteRootUri().ToString(), "checkout/payment.mp3")
: await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.SoundFileId);
}
<audio controls src="@soundUrl" style="height:2.1rem;max-width:10.5rem;"></audio>
</div> </div>
<span asp-validation-for="SoundFile" class="text-danger"></span> <span asp-validation-for="SoundFile" class="text-danger"></span>
} }

View File

@@ -46,7 +46,7 @@
<div class="form-group"> <div class="form-group">
<div class="d-flex align-items-center justify-content-between gap-2"> <div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="LogoFile" class="form-label"></label> <label asp-for="LogoFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.LogoFileId)) @if (!string.IsNullOrEmpty(Model.LogoUrl))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true"> <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true">
<span class="fa fa-times"></span> Remove <span class="fa fa-times"></span> Remove
@@ -57,9 +57,9 @@
{ {
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<input asp-for="LogoFile" type="file" class="form-control flex-grow"> <input asp-for="LogoFile" type="file" class="form-control flex-grow">
@if (!string.IsNullOrEmpty(Model.LogoFileId)) @if (!string.IsNullOrEmpty(Model.LogoUrl))
{ {
<img src="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.LogoFileId))" alt="@Model.StoreName Logo" style="height:2.1rem;max-width:10.5rem;"/> <img src="@Model.LogoUrl" alt="@Model.StoreName Logo" style="height:2.1rem;max-width:10.5rem;"/>
} }
</div> </div>
<span asp-validation-for="LogoFile" class="text-danger"></span> <span asp-validation-for="LogoFile" class="text-danger"></span>
@@ -73,7 +73,7 @@
<div class="form-group"> <div class="form-group">
<div class="d-flex align-items-center justify-content-between gap-2"> <div class="d-flex align-items-center justify-content-between gap-2">
<label asp-for="CssFile" class="form-label"></label> <label asp-for="CssFile" class="form-label"></label>
@if (!string.IsNullOrEmpty(Model.CssFileId)) @if (!string.IsNullOrEmpty(Model.CssUrl))
{ {
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveCssFile" value="true"> <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveCssFile" value="true">
<span class="fa fa-times"></span> Remove <span class="fa fa-times"></span> Remove
@@ -84,9 +84,9 @@
{ {
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<input asp-for="CssFile" type="file" class="form-control flex-grow"> <input asp-for="CssFile" type="file" class="form-control flex-grow">
@if (!string.IsNullOrEmpty(Model.CssFileId)) @if (!string.IsNullOrEmpty(Model.CssUrl))
{ {
<a href="@(await FileService.GetFileUrl(Context.Request.GetAbsoluteRootUri(), Model.CssFileId))" target="_blank" rel="noreferrer noopener" class="text-nowrap">Custom CSS</a> <a href="@Model.CssUrl" target="_blank" rel="noreferrer noopener" class="text-nowrap">Custom CSS</a>
} }
</div> </div>
<span asp-validation-for="LogoFile" class="text-danger"></span> <span asp-validation-for="LogoFile" class="text-danger"></span>

View File

@@ -551,11 +551,6 @@
"description": "Prompt which appears next to the tip amount field if tipping is enabled", "description": "Prompt which appears next to the tip amount field if tipping is enabled",
"example": "Do you want to leave a tip?" "example": "Do you want to leave a tip?"
}, },
"customCSSLink": {
"type": "string",
"description": "Link to a custom CSS stylesheet to be used in the app",
"example": "https://bootswatch.com/4/slate/bootstrap.min.css"
},
"notificationUrl": { "notificationUrl": {
"type": "string", "type": "string",
"description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations" "description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations"
@@ -564,10 +559,6 @@
"type": "string", "type": "string",
"description": "URL user is redirected to once invoice is paid" "description": "URL user is redirected to once invoice is paid"
}, },
"embeddedCSS": {
"type": "string",
"description": "Custom CSS embedded into the app"
},
"redirectAutomatically": { "redirectAutomatically": {
"type": "boolean", "type": "boolean",
"description": "Whether user is redirected to specified redirect URL automatically after the invoice is paid", "description": "Whether user is redirected to specified redirect URL automatically after the invoice is paid",
@@ -627,18 +618,10 @@
"description": "Target amount for the crowdfund", "description": "Target amount for the crowdfund",
"example": 420.69 "example": 420.69
}, },
"customCSSLink": {
"type": "string",
"description": "Link to a custom CSS stylesheet to be used in the app"
},
"mainImageUrl": { "mainImageUrl": {
"type": "string", "type": "string",
"description": "URL for image used as a cover image for the app" "description": "URL for image used as a cover image for the app"
}, },
"embeddedCSS": {
"type": "string",
"description": "Custom CSS embedded into the app"
},
"perks": { "perks": {
"type": "object", "type": "object",
"description": "JSON of perks available in the app", "description": "JSON of perks available in the app",
@@ -875,16 +858,6 @@
"default": "Do you want to leave a tip?", "default": "Do you want to leave a tip?",
"nullable": true "nullable": true
}, },
"customCSSLink": {
"type": "string",
"description": "Link to a custom CSS stylesheet to be used in the app",
"nullable": true
},
"embeddedCSS": {
"type": "string",
"description": "Custom CSS to embed into the app",
"nullable": true
},
"notificationUrl": { "notificationUrl": {
"type": "string", "type": "string",
"description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations", "description": "Callback notification url to POST to once when invoice is paid for and once when there are enough blockchain confirmations",
@@ -966,21 +939,11 @@
"example": 420, "example": 420,
"nullable": true "nullable": true
}, },
"customCSSLink": {
"type": "string",
"description": "Link to a custom CSS stylesheet to be used in the app",
"nullable": true
},
"mainImageUrl": { "mainImageUrl": {
"type": "string", "type": "string",
"description": "URL for image to be used as a cover image for the app", "description": "URL for image to be used as a cover image for the app",
"nullable": true "nullable": true
}, },
"embeddedCSS": {
"type": "string",
"description": "Custom CSS to embed into the app",
"nullable": true
},
"perksTemplate": { "perksTemplate": {
"type": "string", "type": "string",
"description": "YAML template of perks available in the app", "description": "YAML template of perks available in the app",

View File

@@ -454,19 +454,6 @@
"nullable": true, "nullable": true,
"allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ] "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ]
}, },
"embeddedCSS": {
"type": "string",
"description": "Custom CSS styling for the payment request",
"nullable": true,
"format": "css",
"maximum": 500
},
"customCSSLink": {
"type": "string",
"description": "Custom CSS link for styling the payment request",
"nullable": true,
"format": "uri"
},
"allowCustomPaymentAmounts": { "allowCustomPaymentAmounts": {
"type": "boolean", "type": "boolean",
"description": "Whether to allow users to create invoices that partially pay the payment request ", "description": "Whether to allow users to create invoices that partially pay the payment request ",

View File

@@ -411,6 +411,30 @@
"description": "The support URI of the store, can contain the placeholders `{OrderId}` and `{InvoiceId}`. Can be any valid URI, such as a website, email, and nostr.", "description": "The support URI of the store, can contain the placeholders `{OrderId}` and `{InvoiceId}`. Can be any valid URI, such as a website, email, and nostr.",
"format": "uri" "format": "uri"
}, },
"logoUrl": {
"type": "string",
"nullable": true,
"description": "Absolute URL to a logo file or a reference to an uploaded file id with `fileid:ID`",
"format": "uri"
},
"cssUrl": {
"type": "string",
"nullable": true,
"description": "Absolute URL to CSS file to customize the public/customer-facing pages of the store. (Invoice, Payment Request, Pull Payment, etc.) or a reference to an uploaded file id with `fileid:ID`",
"format": "uri"
},
"paymentSoundUrl": {
"type": "string",
"nullable": true,
"description": "Absolute URL to a sound file or a reference to an uploaded file id with `fileid:ID`",
"format": "uri"
},
"brandColor": {
"type": "string",
"description": "The brand color of the store in HEX format",
"nullable": true,
"example": "#F7931A"
},
"defaultCurrency": { "defaultCurrency": {
"type": "string", "type": "string",
"description": "The default currency of the store", "description": "The default currency of the store",