From b984dab2be1b6051f371fb241fb508f142e42266 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sat, 13 Jun 2026 12:08:23 +0200 Subject: [PATCH 1/3] Updated valid payfast addresses --- LiteCharms.Features.Tests.Common/appsettings.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/LiteCharms.Features.Tests.Common/appsettings.json b/LiteCharms.Features.Tests.Common/appsettings.json index cc03b14..5bc3088 100644 --- a/LiteCharms.Features.Tests.Common/appsettings.json +++ b/LiteCharms.Features.Tests.Common/appsettings.json @@ -4,8 +4,6 @@ "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" From 99c0508f6ff2775577c747611ad014d86ea57385 Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sat, 13 Jun 2026 15:45:59 +0200 Subject: [PATCH 2/3] Implemented separate signature validator --- .../LiteCharms.Features.MidrandBooks.csproj | 1 + .../Payments/PayfastService.cs | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj index c2f0ac7..564430a 100644 --- a/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj +++ b/LiteCharms.Features.MidrandBooks/LiteCharms.Features.MidrandBooks.csproj @@ -148,6 +148,7 @@ + diff --git a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs index 5cb585c..cbca90d 100644 --- a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs +++ b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs @@ -3,6 +3,7 @@ using LiteCharms.Features.Api.Configuration; using LiteCharms.Features.Hasher; using LiteCharms.Features.MidrandBooks.Payments.Models; using LiteCharms.Features.MidrandBooks.Postgres; +using Microsoft.AspNetCore.Http; namespace LiteCharms.Features.MidrandBooks.Payments; @@ -48,6 +49,35 @@ public sealed partial class PayfastService(IDbContextFactory x.Key, x => x.Value.ToString()); + + if (!formFields.TryGetValue("signature", out string? incomingSignature)) + return false; + + var stringBuilder = new StringBuilder(); + + foreach (var key in formFields.Keys) + { + if (key.Equals("signature", StringComparison.OrdinalIgnoreCase)) + continue; + + string encodedVal = HttpUtility.UrlEncode(formFields[key].Trim()); + string cleanVal = PercentEncodingRegex.Replace(encodedVal, m => m.Value.ToUpperInvariant()); + + stringBuilder.Append($"{key}={cleanVal}&"); + } + + string encodedPassphrase = HttpUtility.UrlEncode(passphrase.Trim()); + string safePassphrase = PercentEncodingRegex.Replace(encodedPassphrase, m => m.Value.ToUpperInvariant()); + + stringBuilder.Append($"passphrase={safePassphrase}"); + + string generatedSignature = HashService.ToMd5Hash(stringBuilder.ToString()).Value; + return incomingSignature.Equals(generatedSignature, StringComparison.OrdinalIgnoreCase); + } + public async ValueTask> ValidateReferrerIpAsync(string remoteIpAddress, bool allowLoopback = false, CancellationToken cancellationToken = default) { if(payfastOptions.Value?.ValidHosts?.Length == 0) From 59fc0432b4bee005999d3b860a887c648bb6664e Mon Sep 17 00:00:00 2001 From: Khwezi Mngoma Date: Sat, 13 Jun 2026 15:49:45 +0200 Subject: [PATCH 3/3] ensure alphabetical sorting --- .../Payments/PayfastService.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs index cbca90d..5ebaa7e 100644 --- a/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs +++ b/LiteCharms.Features.MidrandBooks/Payments/PayfastService.cs @@ -3,7 +3,6 @@ using LiteCharms.Features.Api.Configuration; using LiteCharms.Features.Hasher; using LiteCharms.Features.MidrandBooks.Payments.Models; using LiteCharms.Features.MidrandBooks.Postgres; -using Microsoft.AspNetCore.Http; namespace LiteCharms.Features.MidrandBooks.Payments; @@ -51,7 +50,10 @@ public sealed partial class PayfastService(IDbContextFactory x.Key, x => x.Value.ToString()); + var formFields = new Dictionary(StringComparer.Ordinal); + + foreach (var file in request.Form) + formFields.Add(file.Key, file.Value.ToString()); if (!formFields.TryGetValue("signature", out string? incomingSignature)) return false; @@ -63,18 +65,21 @@ public sealed partial class PayfastService(IDbContextFactory m.Value.ToUpperInvariant()); + string rawValue = formFields[key] ?? string.Empty; + + string encodedVal = HttpUtility.UrlEncode(rawValue.Trim()); + string cleanVal = PercentEncodingRegex.Replace(encodedVal, m => m.Value.ToUpperInvariant()); stringBuilder.Append($"{key}={cleanVal}&"); } string encodedPassphrase = HttpUtility.UrlEncode(passphrase.Trim()); - string safePassphrase = PercentEncodingRegex.Replace(encodedPassphrase, m => m.Value.ToUpperInvariant()); + string safePassphrase = PercentEncodingRegex.Replace(encodedPassphrase, m => m.Value.ToUpperInvariant()); stringBuilder.Append($"passphrase={safePassphrase}"); string generatedSignature = HashService.ToMd5Hash(stringBuilder.ToString()).Value; + return incomingSignature.Equals(generatedSignature, StringComparison.OrdinalIgnoreCase); }