From 4d2b37ace7e54fdd94ca435c4221699a32a90fac Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Fri, 12 Jun 2026 20:48:12 +0200 Subject: [PATCH] Completed token service --- ...PaymentConfirmationReceivedEventHandler.cs | 12 +++++----- .../Payments/PayfastService.cs | 10 ++++---- LiteCharms.Features.Tests.Common/Fixture.cs | 1 + .../appsettings.json | 23 +++++++++++-------- .../LiteCharms.Features.Tests.csproj | 1 + .../PayfastFeatureTests.cs | 18 +++++++++++++++ .../Api/Configuration/PayfastSettings.cs | 14 +++++++++++ LiteCharms.Features/Extensions/Api.cs | 9 ++++++++ 8 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 LiteCharms.Features.Tests/PayfastFeatureTests.cs create mode 100644 LiteCharms.Features/Api/Configuration/PayfastSettings.cs diff --git a/LiteCharms.Features.MidrandBooks/Payments/Events/Handlers/PayfastPaymentConfirmationReceivedEventHandler.cs b/LiteCharms.Features.MidrandBooks/Payments/Events/Handlers/PayfastPaymentConfirmationReceivedEventHandler.cs index cae4c21..8211b4e 100644 --- a/LiteCharms.Features.MidrandBooks/Payments/Events/Handlers/PayfastPaymentConfirmationReceivedEventHandler.cs +++ b/LiteCharms.Features.MidrandBooks/Payments/Events/Handlers/PayfastPaymentConfirmationReceivedEventHandler.cs @@ -1,15 +1,16 @@ -using LiteCharms.Features.Hasher; -using LiteCharms.Features.Hasher.Configuration; +using LiteCharms.Features.Api.Configuration; +using LiteCharms.Features.Hasher; using LiteCharms.Features.Mediator; using LiteCharms.Features.MidrandBooks.Orders; using LiteCharms.Features.MidrandBooks.Payments.Models; namespace LiteCharms.Features.MidrandBooks.Payments.Events.Handlers; -public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvider services, IOptions hasherOptions, ILogger logger) : +public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvider services, + IOptions payfastOptions, ILogger logger) : INotificationHandler { - private readonly HasherSettings hasherSettings = hasherOptions.Value; + private readonly PayfastSettings pasfastSettings = payfastOptions.Value; public async ValueTask Handle(PayfastPaymentConfirmationReceivedEvent notification, CancellationToken cancellationToken) { @@ -25,7 +26,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi var payload = notification.Payload ?? throw new Exception("Payload metadata context context is null."); var dict = payload.ToParamDictionary(); - var localSignature = PayfastService.GenerateSignature(dict, hasherSettings.PayfastPassphrase); + var localSignature = PayfastService.GenerateSignature(dict, pasfastSettings.Passphrase); if (localSignature.IsFailed) throw new Exception("Failed to generate local signature for incoming webhook payload."); @@ -159,6 +160,5 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi logger.LogInformation("Webhook validation pipeline passed checks successfully, logged entry to ledger with status: {Status}", status); } activity?.SetStatus(ActivityStatusCode.Ok); - } } diff --git a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs index 7167285..2d8ec63 100644 --- a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs +++ b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs @@ -1,4 +1,5 @@ using LiteCharms.Features.Abstractions; +using LiteCharms.Features.Api.Configuration; using LiteCharms.Features.Hasher; using LiteCharms.Features.MidrandBooks.Payments.Models; using LiteCharms.Features.MidrandBooks.Postgres; @@ -6,13 +7,11 @@ using LiteCharms.Features.MidrandBooks.Postgres; namespace LiteCharms.Features.MidrandBooks.Payments; public sealed partial class PayfastService(IDbContextFactory contextFactory, - ILogger logger, IHttpClientFactory httpClientFactory, IConfiguration configuration) : IService + IOptions payfastOptions, ILogger logger, IHttpClientFactory httpClientFactory) : IService { [GeneratedRegex(@"%[0-9A-Fa-f]{2}", RegexOptions.None, matchTimeoutMilliseconds: 1000)] public static partial Regex PercentEncodingRegex { get; } - public readonly string[] ValidHosts = configuration.GetSection("ValidPayfastHosts").Get() ?? []; - public async ValueTask> WriteLedgerEntryAsync(CreateGatewayLedgerEntry request, CancellationToken cancellationToken = default) { try @@ -51,6 +50,9 @@ public sealed partial class PayfastService(IDbContextFactory> ValidateReferrerIpAsync(string remoteIpAddress, bool allowLoopback = false, CancellationToken cancellationToken = default) { + if(payfastOptions.Value?.ValidHosts?.Length == 0) + return Result.Fail("Valid payfast hosts not configured."); + if (string.IsNullOrWhiteSpace(remoteIpAddress)) return Result.Fail("Remote IP address is null or whitespace."); @@ -58,7 +60,7 @@ public sealed partial class PayfastService(IDbContextFactory(); - foreach (var host in ValidHosts) + foreach (var host in payfastOptions.Value!.ValidHosts!) { try { diff --git a/LiteCharms.Features.Tests.Common/Fixture.cs b/LiteCharms.Features.Tests.Common/Fixture.cs index 507d4e1..5694f5f 100644 --- a/LiteCharms.Features.Tests.Common/Fixture.cs +++ b/LiteCharms.Features.Tests.Common/Fixture.cs @@ -36,6 +36,7 @@ public class Fixture : IDisposable .AddHashServices(Configuration) .AddLiteCharmsApiSecurity(Configuration) .AddSecurityApiSdk(Configuration) + .AddPayfastServices(Configuration) .BuildServiceProvider(); ; Mediator = Services.GetRequiredService(); diff --git a/LiteCharms.Features.Tests.Common/appsettings.json b/LiteCharms.Features.Tests.Common/appsettings.json index 634da64..cc03b14 100644 --- a/LiteCharms.Features.Tests.Common/appsettings.json +++ b/LiteCharms.Features.Tests.Common/appsettings.json @@ -1,22 +1,25 @@ { + "PayfastSettings": { + "CheckoutUrl": "https://sandbox.payfast.co.za/eng/process", + "ValidHosts": [ + "www.payfast.co.za", + "sandbox.payfast.co.za", + "w1w.payfast.co.za", + "w2w.payfast.co.za", + "ips.payfast.co.za", + "api.payfast.co.za", + "payment.payfast.io" + ] + }, "LiteCharmsSettings": { "Authority": "https://sts.security.khongisa.co.za", "Audience": "midrandbooks-api" }, "LiteCharmsClientSettings": { - "Authority": "https://sts.security.khongisa.co.za", + "Authority": "https://sts.security.khongisa.co.za", "GrantType": "client_credentials", "Scope": "midrandbooks-api" }, - "ValidPayfastHosts": [ - "www.payfast.co.za", - "sandbox.payfast.co.za", - "w1w.payfast.co.za", - "w2w.payfast.co.za", - "ips.payfast.co.za", - "api.payfast.co.za", - "payment.payfast.io" - ], "HasherSettings": { "MinHashLength": 11 }, diff --git a/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj b/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj index ef7e0dc..239381b 100644 --- a/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj +++ b/LiteCharms.Features.Tests/LiteCharms.Features.Tests.csproj @@ -31,6 +31,7 @@ + diff --git a/LiteCharms.Features.Tests/PayfastFeatureTests.cs b/LiteCharms.Features.Tests/PayfastFeatureTests.cs new file mode 100644 index 0000000..e1fec0f --- /dev/null +++ b/LiteCharms.Features.Tests/PayfastFeatureTests.cs @@ -0,0 +1,18 @@ +using LiteCharms.Features.Api.Configuration; +using LiteCharms.Features.Tests.Common; + +namespace LiteCharms.Features.Tests; + +public sealed class PayfastFeatureTests(Fixture fixture) : IClassFixture +{ + private readonly PayfastSettings payfastSettings = fixture.Services.GetRequiredService>().Value; + + [IntegrationFact] + public void PayfastSettings_ShouldFail_IfNotLoaded() + { + Assert.NotEmpty(payfastSettings.CheckoutUrl!); + Assert.NotEmpty(payfastSettings.MerchantId!); + Assert.NotEmpty(payfastSettings.MerchantKey!); + Assert.NotEmpty(payfastSettings.Passphrase!); + } +} diff --git a/LiteCharms.Features/Api/Configuration/PayfastSettings.cs b/LiteCharms.Features/Api/Configuration/PayfastSettings.cs new file mode 100644 index 0000000..0a9d5c1 --- /dev/null +++ b/LiteCharms.Features/Api/Configuration/PayfastSettings.cs @@ -0,0 +1,14 @@ +namespace LiteCharms.Features.Api.Configuration; + +public sealed class PayfastSettings +{ + public string? CheckoutUrl { get; set; } + + public string? Passphrase { get; set; } + + public string? MerchantId { get; set; } + + public string? MerchantKey { get; set; } + + public string[]? ValidHosts { get; set; } +} diff --git a/LiteCharms.Features/Extensions/Api.cs b/LiteCharms.Features/Extensions/Api.cs index 9fe6af4..f6857bb 100644 --- a/LiteCharms.Features/Extensions/Api.cs +++ b/LiteCharms.Features/Extensions/Api.cs @@ -9,6 +9,15 @@ public static class Api { public const string Books = nameof(Books); public const string Payments = nameof(Payments); + + public static IServiceCollection AddPayfastServices(this IServiceCollection services, IConfiguration configuration) + { + var configSection = configuration.GetSection(nameof(PayfastSettings)); + + services.Configure(configSection); + + return services; + } public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration) {