This commit is contained in:
+6
-6
@@ -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<HasherSettings> hasherOptions, ILogger<PayfastPaymentConfirmationReceivedEvent> logger) :
|
||||
public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvider services,
|
||||
IOptions<PayfastSettings> payfastOptions, ILogger<PayfastPaymentConfirmationReceivedEvent> logger) :
|
||||
INotificationHandler<PayfastPaymentConfirmationReceivedEvent>
|
||||
{
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MidrandBooksDbContext> contextFactory,
|
||||
ILogger<PayfastService> logger, IHttpClientFactory httpClientFactory, IConfiguration configuration) : IService
|
||||
IOptions<PayfastSettings> payfastOptions, ILogger<PayfastService> 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<string[]>() ?? [];
|
||||
|
||||
public async ValueTask<Result<long>> WriteLedgerEntryAsync(CreateGatewayLedgerEntry request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
@@ -51,6 +50,9 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
|
||||
|
||||
public async ValueTask<Result<bool>> ValidateReferrerIpAsync(string remoteIpAddress, bool allowLoopback = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if(payfastOptions.Value?.ValidHosts?.Length == 0)
|
||||
return Result.Fail<bool>("Valid payfast hosts not configured.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(remoteIpAddress))
|
||||
return Result.Fail<bool>("Remote IP address is null or whitespace.");
|
||||
|
||||
@@ -58,7 +60,7 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
|
||||
{
|
||||
var validIps = new HashSet<IPAddress>();
|
||||
|
||||
foreach (var host in ValidHosts)
|
||||
foreach (var host in payfastOptions.Value!.ValidHosts!)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ public class Fixture : IDisposable
|
||||
.AddHashServices(Configuration)
|
||||
.AddLiteCharmsApiSecurity(Configuration)
|
||||
.AddSecurityApiSdk(Configuration)
|
||||
.AddPayfastServices(Configuration)
|
||||
.BuildServiceProvider(); ;
|
||||
|
||||
Mediator = Services.GetRequiredService<IMediator>();
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
{
|
||||
"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"
|
||||
@@ -8,15 +20,6 @@
|
||||
"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
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<Using Include="System.Text" />
|
||||
<Using Include="Mediator" />
|
||||
<Using Include="Xunit.Abstractions" />
|
||||
<Using Include="Microsoft.Extensions.Options" />
|
||||
<Using Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<Using Include="Microsoft.Extensions.Configuration" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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<Fixture>
|
||||
{
|
||||
private readonly PayfastSettings payfastSettings = fixture.Services.GetRequiredService<IOptions<PayfastSettings>>().Value;
|
||||
|
||||
[IntegrationFact]
|
||||
public void PayfastSettings_ShouldFail_IfNotLoaded()
|
||||
{
|
||||
Assert.NotEmpty(payfastSettings.CheckoutUrl!);
|
||||
Assert.NotEmpty(payfastSettings.MerchantId!);
|
||||
Assert.NotEmpty(payfastSettings.MerchantKey!);
|
||||
Assert.NotEmpty(payfastSettings.Passphrase!);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -10,6 +10,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<PayfastSettings>(configSection);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddSecurityApiSdk(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var configSection = configuration.GetSection(nameof(LiteCharmsClientSettings));
|
||||
|
||||
Reference in New Issue
Block a user