Added payment gateway ledger service to payments feature

This commit is contained in:
Khwezi Mngoma
2026-06-02 23:44:45 +02:00
parent 5ab2d29aac
commit 0ed04211bf
30 changed files with 3420 additions and 256 deletions
+1
View File
@@ -19,6 +19,7 @@ public enum LedgerStatuses : int
Cancelled = 4,
Failed = 5,
Partial = 6,
Completed = 7,
}
public enum PaymentStatuses : int
+1 -80
View File
@@ -1,6 +1,5 @@
using LiteCharms.Features.Hasher;
using LiteCharms.Features.Hasher.Configuration;
using LiteCharms.Features.Models;
namespace LiteCharms.Features.Extensions;
@@ -20,83 +19,5 @@ public static class Hash
services.AddSingleton<HashService>();
return services;
}
public static string ToRawPayfastPayload(this PayfastWebhookPayload input, string passphrase)
{
var parameters = new List<string>();
if (!string.IsNullOrWhiteSpace(input.Amount))
parameters.Add($"amount={WebUtility.UrlEncode(input.Amount)}");
if (!string.IsNullOrWhiteSpace(input.ItemName))
parameters.Add($"item_name={WebUtility.UrlEncode(input.ItemName)}");
if (!string.IsNullOrWhiteSpace(input.MPaymentId))
parameters.Add($"m_payment_id={WebUtility.UrlEncode(input.MPaymentId)}");
string payload = string.Join("&", parameters);
if (!string.IsNullOrWhiteSpace(passphrase))
payload += $"&passphrase={WebUtility.UrlEncode(passphrase)}";
return payload;
}
public static (PayfastWebhookPayload Payload, string Passphrase) FromRawPayfastPayload(this string rawPayload)
{
string passphrase = string.Empty;
var payload = new PayfastWebhookPayload();
if (string.IsNullOrWhiteSpace(rawPayload)) return (payload, passphrase);
var segments = rawPayload.Split('&', StringSplitOptions.RemoveEmptyEntries);
foreach (var segment in segments)
{
int delimiterIndex = segment.IndexOf('=');
if (delimiterIndex == -1)
continue;
string key = segment[..delimiterIndex].Trim();
string rawValue = segment[(delimiterIndex + 1)..];
string decodedValue = WebUtility.UrlDecode(rawValue);
switch (key.ToLowerInvariant())
{
case "amount":
payload.Amount = decodedValue;
break;
case "item_name":
payload.ItemName = decodedValue;
break;
case "m_payment_id":
payload.MPaymentId = decodedValue;
break;
case "passphrase":
passphrase = decodedValue;
break;
}
}
return (payload, passphrase);
}
public static (PayfastWebhookPayload Payload, string Passphrase) FromRawPayfastPayload(this IFormCollection form)
{
string passphrase = string.Empty;
var payload = new PayfastWebhookPayload();
if (form.IsNullOrEmpty()) return (payload, passphrase);
payload = new PayfastWebhookPayload
{
Amount = form.TryGetValue("amount", out var amountValues) ? amountValues.ToString() : null,
ItemName = form.TryGetValue("item_name", out var itemValues) ? itemValues.ToString() : null,
MPaymentId = form.TryGetValue("m_payment_id", out var paymentIdValues) ? paymentIdValues.ToString() : null,
};
return (payload, passphrase);
}
}
}
+1 -43
View File
@@ -1,13 +1,9 @@
using LiteCharms.Features.Abstractions;
using LiteCharms.Features.Hasher.Configuration;
using LiteCharms.Features.Models;
namespace LiteCharms.Features.Hasher;
public sealed partial class HashService(IHashids hasher, IOptions<HasherSettings> options) : IService
public sealed partial class HashService(IHashids hasher) : IService
{
private readonly HasherSettings settings = options.Value;
[GeneratedRegex(@"\A\b[0-9a-fA-F]+\b\Z")]
private static partial Regex HexHashRegex { get; }
@@ -41,44 +37,6 @@ public sealed partial class HashService(IHashids hasher, IOptions<HasherSettings
return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant());
}
public Result<bool> VerifyPayfastWebhookSignature(PayfastWebhookPayload payload, string incomingSignature)
{
try
{
if (string.IsNullOrWhiteSpace(incomingSignature))
return Result.Fail<bool>("Validation failed: Missing signature string parameter.");
var parameters = new List<string>();
if (!string.IsNullOrWhiteSpace(payload.Amount))
parameters.Add($"amount={WebUtility.UrlEncode(payload.Amount)}");
if (!string.IsNullOrWhiteSpace(payload.ItemName))
parameters.Add($"item_name={WebUtility.UrlEncode(payload.ItemName)}");
if (!string.IsNullOrWhiteSpace(payload.MPaymentId))
parameters.Add($"m_payment_id={WebUtility.UrlEncode(payload.MPaymentId)}");
string signatureString = string.Join("&", parameters);
if (!string.IsNullOrWhiteSpace(settings.PayfastPassphrase))
signatureString += $"&passphrase={WebUtility.UrlEncode(settings.PayfastPassphrase)}";
var localHashResult = ToMd5Hash(signatureString);
if (!localHashResult.IsSuccess)
return Result.Fail<bool>(localHashResult.Errors);
bool isValid = string.Equals(localHashResult.Value, incomingSignature, StringComparison.OrdinalIgnoreCase);
return Result.Ok(isValid);
}
catch (Exception ex)
{
return Result.Fail<bool>(new Error("An error occurred during Payfast MD5 verification.").CausedBy(ex));
}
}
public Result<string> HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input)
? Result.Fail<string>("Input must be a valid hexadecimal string.")
: Result.Ok(hasher.EncodeHex(input));
@@ -147,6 +147,7 @@
<!-- Shared Usings -->
<ItemGroup>
<Using Include="System.Web" />
<Using Include="Microsoft.IdentityModel.Tokens" />
<Using Include="Microsoft.AspNetCore.Http" />
<Using Include="HashidsNet" />
@@ -1,8 +0,0 @@
namespace LiteCharms.Features.Models;
public sealed class PayfastWebhookPayload
{
public string? Amount { get; set; }
public string? ItemName { get; set; }
public string? MPaymentId { get; set; }
}