Add contribution payment options to registries

- Added support for single and amount-specific QR codes.
- Updated `RegistrySettings` model and database schema.
- Enhanced `RegistryAdmin` to configure payment options.
- Introduced `RegistryContributionAmount` for partial fulfillment.
- Updated `RegistryPublic` to display contribution progress.
- Added new models for payment methods and QR code handling.
- Improved privacy by limiting contributor visibility.
This commit is contained in:
Arne Moerman
2026-05-18 23:33:06 +02:00
parent 7dd7b0342f
commit c36e04029b
14 changed files with 1211 additions and 21 deletions
@@ -4,6 +4,7 @@ using BirthList.Web.Data;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace BirthList.Web.Features.Registries;
@@ -210,6 +211,8 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
BankAccountBic = settings?.BankAccountBic,
BankAccountDisplayName = settings?.BankAccountDisplayName,
ShowBankAccountName = settings?.ShowBankAccountName ?? false,
ContributionQrCodeUrl = settings?.ContributionQrCodeUrl,
ContributionAmountQrCodes = ParseContributionAmountQrCodes(settings?.ContributionAmountQrCodesJson),
CurrentUserId = userId,
IsAdmin = isAdmin,
Items = registry.Items
@@ -289,7 +292,9 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
BankAccountIban = settings?.BankAccountIban,
BankAccountBic = settings?.BankAccountBic,
BankAccountDisplayName = settings?.BankAccountDisplayName,
ShowBankAccountName = settings?.ShowBankAccountName ?? false
ShowBankAccountName = settings?.ShowBankAccountName ?? false,
ContributionQrCodeUrl = settings?.ContributionQrCodeUrl,
ContributionAmountQrCodes = ParseContributionAmountQrCodes(settings?.ContributionAmountQrCodesJson).ToList()
};
}
@@ -325,10 +330,68 @@ internal sealed class RegistryService(RegistryDbContext registryDbContext, Appli
settings.BankAccountBic = string.IsNullOrWhiteSpace(model.BankAccountBic) ? null : model.BankAccountBic.Trim();
settings.BankAccountDisplayName = string.IsNullOrWhiteSpace(model.BankAccountDisplayName) ? null : model.BankAccountDisplayName.Trim();
settings.ShowBankAccountName = model.ShowBankAccountName;
settings.ContributionQrCodeUrl = string.IsNullOrWhiteSpace(model.ContributionQrCodeUrl) ? null : model.ContributionQrCodeUrl.Trim();
settings.ContributionAmountQrCodesJson = SerializeContributionAmountQrCodes(model.ContributionAmountQrCodes);
await registryDbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
private static IReadOnlyList<ContributionAmountQrCodeModel> ParseContributionAmountQrCodes(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
try
{
var items = JsonSerializer.Deserialize<List<ContributionAmountQrCodeModel>>(json);
if (items is null)
{
return [];
}
return items
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
}
catch (JsonException)
{
return [];
}
}
private static string? SerializeContributionAmountQrCodes(IEnumerable<ContributionAmountQrCodeModel>? amountQrCodes)
{
if (amountQrCodes is null)
{
return null;
}
var normalized = amountQrCodes
.Where(x => x.Amount > 0 && !string.IsNullOrWhiteSpace(x.QrCodeUrl))
.Select(x => new ContributionAmountQrCodeModel
{
Amount = x.Amount,
QrCodeUrl = x.QrCodeUrl.Trim()
})
.OrderBy(x => x.Amount)
.ToList();
if (normalized.Count == 0)
{
return null;
}
return JsonSerializer.Serialize(normalized);
}
public async Task<IReadOnlyList<RegistryItemEditModel>> GetRegistryItemsAsync(Guid registryId, CancellationToken cancellationToken)
{
var items = await registryDbContext.RegistryItems