Compare commits
67
Commits
4bac14881d
...
1.70.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eac9d533f | ||
|
|
961f03c1c7 | ||
|
|
a0cf847e51 | ||
|
|
24ba609e0c | ||
|
|
29f6d66c44 | ||
|
|
bcfc9ef962 | ||
|
|
b4e967acc9 | ||
|
|
205bbb9f3f | ||
|
|
73ef4b04a9 | ||
|
|
780415b6d4 | ||
|
|
879094073a | ||
|
|
b369dad452 | ||
|
|
c938bfec09 | ||
|
|
adc14038db | ||
|
|
61cb6c2228 | ||
|
|
e7a798b5e9 | ||
|
|
41b6b71b31 | ||
|
|
ee6beef603 | ||
|
|
1c3f3eaf0d | ||
|
|
2e77666d9e | ||
|
|
1977b6b301 | ||
|
|
0ab14d8b63 | ||
|
|
466458e230 | ||
|
|
141d32f591 | ||
|
|
d9e7f225ae | ||
|
|
6ae63e2ad1 | ||
|
|
f5efdde37c | ||
|
|
1592d5dc8f | ||
|
|
50a8a59d92 | ||
|
|
b70d9559b0 | ||
|
|
81d5e8f07c | ||
|
|
20a53942b5 | ||
|
|
9edb2aa4aa | ||
|
|
6ed023f2cf | ||
|
|
2c9f5a846c | ||
|
|
41f7c05be3 | ||
|
|
1a03355e84 | ||
|
|
7743c3178e | ||
|
|
ab3d8e6e9a | ||
|
|
db4c348288 | ||
|
|
6683234642 | ||
|
|
6ddbb9479a | ||
|
|
6c7349a0f8 | ||
|
|
e97fd6cd3f | ||
|
|
184c7c252a | ||
|
|
bfe8c458d6 | ||
|
|
e6e0475db1 | ||
|
|
5090c60797 | ||
|
|
9432252e15 | ||
|
|
47111a1a3a | ||
|
|
6eb3d50375 | ||
|
|
4deb732804 | ||
|
|
20d9387d0b | ||
|
|
9f6d0ccaa0 | ||
|
|
1acbc4d213 | ||
|
|
8c99668fac | ||
|
|
ad44f46204 | ||
|
|
49d999c1e3 | ||
|
|
9ed4777a18 | ||
|
|
0cf44f68cc | ||
|
|
41ed5a4288 | ||
|
|
bbcba5e06c | ||
|
|
502cc326dd | ||
|
|
4675d4c5fc | ||
|
|
f80bb2fff9 | ||
|
|
6767906b0d | ||
|
|
a344af4498 |
+13
-7
@@ -1,17 +1,21 @@
|
||||
using LiteCharms.Features.Hasher;
|
||||
using LiteCharms.Features.Hasher.Configuration;
|
||||
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<HasherSettings> hasherOptions, ILogger<PayfastPaymentConfirmationReceivedEvent> logger) :
|
||||
INotificationHandler<PayfastPaymentConfirmationReceivedEvent>
|
||||
{
|
||||
private readonly HasherSettings hasherSettings = hasherOptions.Value;
|
||||
|
||||
public async ValueTask Handle(PayfastPaymentConfirmationReceivedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
using var activity = MediatorTelemetry.Source.StartActivity($"Quartz: {typeof(PayfastPaymentConfirmationReceivedEvent).Name}");
|
||||
activity?.SetTag("event.correlation_id", notification.CorrelationId);
|
||||
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var hashService = scope.ServiceProvider.GetRequiredService<HashService>();
|
||||
var orderService = scope.ServiceProvider.GetRequiredService<OrderService>();
|
||||
@@ -23,7 +27,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
|
||||
var dict = payload.ToParamDictionary();
|
||||
var localSignature = PayfastService.GenerateSignature(dict, hasherSettings.PayfastPassphrase);
|
||||
|
||||
if(localSignature.IsFailed)
|
||||
if (localSignature.IsFailed)
|
||||
throw new Exception("Failed to generate local signature for incoming webhook payload.");
|
||||
|
||||
if (!string.Equals(localSignature.Value, payload.Signature, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -63,7 +67,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
|
||||
{
|
||||
var isHostValid = await payfastService.ValidateReferrerIpAsync(notification.RemoteIpAddress!, notification.AllowLoopback, cancellationToken);
|
||||
|
||||
if (isHostValid.IsFailed)
|
||||
if (isHostValid.IsFailed)
|
||||
throw new Exception("Security validation exception: Webhook packet source address failed cluster validation checks.");
|
||||
|
||||
if (!isHostValid.Value)
|
||||
@@ -71,7 +75,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
|
||||
|
||||
var isAmountValid = payfastService.ValidatePaymentAmount(orderResult.Value.Total, payload.AmountGross);
|
||||
|
||||
if (!isAmountValid.Value)
|
||||
if (!isAmountValid.Value)
|
||||
throw new Exception("Security validation exception: Transaction cost variance bounds breached.");
|
||||
|
||||
var paramList = new List<string>();
|
||||
@@ -91,8 +95,8 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
|
||||
|
||||
var serverConfirmation = await payfastService.ValidateServerConfirmationAsync(rawParamString, isSandbox: true, cancellationToken);
|
||||
|
||||
if (serverConfirmation.IsFailed)
|
||||
throw new Exception("Security validation exception: Payfast central handshake server rejected payload legitimacy.");
|
||||
if (serverConfirmation.IsFailed)
|
||||
throw new Exception("Security validation exception: Payfast central handshake server rejected payload legitimacy.");
|
||||
}
|
||||
|
||||
await payfastService.WriteLedgerEntryAsync(new CreateGatewayLedgerEntry
|
||||
@@ -105,7 +109,7 @@ public sealed class PayfastPaymentConfirmationReceivedEventHandler(IServiceProvi
|
||||
AmountFee = fee,
|
||||
AmountGross = gross,
|
||||
AmountNet = net,
|
||||
PaymentStatus = status,
|
||||
PaymentStatus = status,
|
||||
}, cancellationToken);
|
||||
|
||||
if (status.Equals("COMPLETE", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -154,5 +158,7 @@ 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace LiteCharms.Features.Abstractions;
|
||||
|
||||
public interface IJobOrchestrator
|
||||
{
|
||||
ValueTask SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent;
|
||||
|
||||
ValueTask ScheduleAsync<TNotification>(TNotification notification, string cronExpression, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent;
|
||||
|
||||
ValueTask<bool> InterruptAsync(string eventName, string? correlationId = null, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using LiteCharms.Features.Quartz;
|
||||
using LiteCharms.Features.Quartz.Abstractions;
|
||||
using LiteCharms.Features.Abstractions;
|
||||
using LiteCharms.Features.Quartz;
|
||||
using static LiteCharms.Features.Extensions.Postgres;
|
||||
|
||||
namespace LiteCharms.Features.Extensions;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using LiteCharms.Features.Abstractions;
|
||||
|
||||
namespace LiteCharms.Features.Quartz.Abstractions;
|
||||
|
||||
public interface IJobOrchestrator
|
||||
{
|
||||
Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent;
|
||||
|
||||
Task ScheduleAsync<TNotification>(TNotification notification, string cronExpression, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent;
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using LiteCharms.Features.Abstractions;
|
||||
using LiteCharms.Features.Quartz.Abstractions;
|
||||
|
||||
namespace LiteCharms.Features.Quartz;
|
||||
|
||||
public sealed class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOrchestrator
|
||||
{
|
||||
public async Task SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
public async ValueTask SendAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent
|
||||
{
|
||||
var chainedJobGroup = "onetime-jobs";
|
||||
@@ -19,17 +18,18 @@ public sealed class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOr
|
||||
.WithDescription($"Correlation ID: {notification.CorrelationId}")
|
||||
.UsingJobData(new JobDataMap { ["Payload"] = JsonSerializer.Serialize(notification) })
|
||||
.DisallowConcurrentExecution()
|
||||
.RequestRecovery()
|
||||
.Build();
|
||||
|
||||
var trigger = global::Quartz.TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.StartNow()
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(job, new List<ITrigger> { trigger }.AsReadOnly(), replace: true, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task ScheduleAsync<TNotification>(TNotification notification, string cronExpression, CancellationToken cancellationToken = default)
|
||||
public async ValueTask ScheduleAsync<TNotification>(TNotification notification, string cronExpression, CancellationToken cancellationToken = default)
|
||||
where TNotification : IEvent
|
||||
{
|
||||
var chainedJobGroup = "scheduled-jobs";
|
||||
@@ -63,4 +63,25 @@ public sealed class JobOrchestrator(ISchedulerFactory schedulerFactory) : IJobOr
|
||||
else
|
||||
await scheduler.ScheduleJob(job, new List<ITrigger> { trigger }.AsReadOnly(), replace: true, cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask<bool> InterruptAsync(string eventName, string? correlationId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
|
||||
|
||||
var jobKeyName = string.Empty;
|
||||
var jobGroup = string.Empty;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(correlationId))
|
||||
{
|
||||
jobKeyName = $"{eventName.ToLower(CultureInfo.InvariantCulture)}-{correlationId.ToLower(CultureInfo.InvariantCulture)}";
|
||||
jobGroup = "onetime-jobs";
|
||||
}
|
||||
else
|
||||
{
|
||||
jobKeyName = eventName.ToLower(CultureInfo.InvariantCulture);
|
||||
jobGroup = "scheduled-jobs";
|
||||
}
|
||||
|
||||
return await scheduler.Interrupt(JobKey.Create(jobKeyName, jobGroup), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ public sealed class MediatorJob<TNotification>(IMediator mediator) : IJob where
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
if (context.Recovering)
|
||||
Trace.WriteLine($"CRITICAL RECOVERY: Resurrecting job '{typeof(TNotification).Name}' after a previous cluster node crashed mid-execution.");
|
||||
|
||||
var data = context.MergedJobDataMap["Payload"] as string;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data))
|
||||
@@ -21,17 +24,28 @@ public sealed class MediatorJob<TNotification>(IMediator mediator) : IJob where
|
||||
|
||||
if (notification is null)
|
||||
{
|
||||
Trace.WriteLine("Notification could not be JSon converted from data string, job ended");
|
||||
Trace.WriteLine("Notification could not be Json converted from data string, job ended");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var activity = MediatorTelemetry.Source.StartActivity($"Quartz: {typeof(TNotification).Name}");
|
||||
|
||||
|
||||
using var activity = MediatorTelemetry.Source.StartActivity(typeof(TNotification).Name);
|
||||
|
||||
activity?.SetTag("event.correlation_id", notification.CorrelationId);
|
||||
|
||||
await mediator.Publish(notification, context.CancellationToken);
|
||||
try
|
||||
{
|
||||
await mediator.Publish(notification, context.CancellationToken);
|
||||
|
||||
Trace.WriteLine("Job published");
|
||||
Trace.WriteLine("Job published successfully");
|
||||
}
|
||||
catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Trace.WriteLine($"Job '{typeof(TNotification).Name}' was gracefully interrupted by the cluster control plane.");
|
||||
|
||||
activity?.SetStatus(ActivityStatusCode.Ok);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ public sealed class RetryJobListener : IJobListener
|
||||
|
||||
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context.CancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (jobException is not null && context.RefireCount < RetryCount)
|
||||
jobException.RefireImmediately = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user