Compare commits

..

10 Commits

Author SHA1 Message Date
khwezi 3deae15f5a Merge pull request 'Removed automatic LocalStorageService registration' (#107) from payments into master
Reviewed-on: #107
2026-06-13 10:19:13 +02:00
Khwezi Mngoma 8e1df7938b Removed automatic LocalStorageService registration
continuous-integration/drone/pr Build is passing
2026-06-13 10:18:42 +02:00
khwezi d9f2d32c76 Merge pull request 'Refactored registration of Features service from Scoped to Transient' (#106) from payments into master
Reviewed-on: #106
2026-06-13 10:07:27 +02:00
Khwezi Mngoma 9296f0331e Refactored registration of Features service from Scoped to Transient
continuous-integration/drone/pr Build is passing
2026-06-13 10:06:54 +02:00
khwezi 1ace61baa5 Merge pull request 'Honoring the mandatory field sequence' (#105) from payments into master
Reviewed-on: #105
2026-06-12 23:30:43 +02:00
Khwezi Mngoma e3e49b8db2 Honoring the mandatory field sequence
continuous-integration/drone/pr Build is passing
2026-06-12 23:30:13 +02:00
khwezi 2ed15b548f Merge pull request 'Refactored PayfastService.GenerateSignature()' (#104) from payments into master
Reviewed-on: #104
2026-06-12 23:27:21 +02:00
Khwezi Mngoma 7d2bc7f1f2 Refactored PayfastService.GenerateSignature()
continuous-integration/drone/pr Build is passing
2026-06-12 23:26:54 +02:00
khwezi ef2428f8e3 Merge pull request 'Refactored GenerateSignature' (#103) from payments into master
Reviewed-on: #103
2026-06-12 23:20:08 +02:00
Khwezi Mngoma 5edff5e272 Refactored GenerateSignature
continuous-integration/drone/pr Build is passing
2026-06-12 23:19:40 +02:00
3 changed files with 54 additions and 27 deletions
@@ -11,20 +11,12 @@ public class CustomerServiceFeatureTests(Fixture fixture) : IClassFixture<Fixtur
[IntegrationFact]
public async Task CreateCustomerAsync_ShouldReturn_ResultWithCustomerId()
{
//var request = new CreateCustomer
//{
// Company = "Book Lovers",
// Email = "hank@booklovers.com",
// Phone = "555 1245 8577",
// Website = "https://www.booklovers.com"
//};
var request = new CreateCustomer
{
//Company = "Book Lovers",
Company = "Book Lovers",
Email = "hank@booklovers.com",
//Phone = "555 1245 8577",
//Website = "https://www.booklovers.com"
Phone = "555 1245 8577",
Website = "https://www.booklovers.com"
};
var result = await customerService.CreateCustomerAsync(request, fixture.CancellationToken);
@@ -147,18 +147,56 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
{
var pfOutput = new StringBuilder();
foreach (var kvp in data)
// Define the exact structural sequence mandated by Payfast's documentation
string[] mandatorySequence =
[
"merchant_id",
"merchant_key",
"return_url",
"cancel_url",
"notify_url",
"name_first",
"name_last",
"email_address",
"cell_number",
"m_payment_id",
"amount",
"item_name",
"item_description",
"custom_int1",
"custom_int2",
"custom_int3",
"custom_int4",
"custom_int5",
"custom_str1",
"custom_str2",
"custom_str3",
"custom_str4",
"custom_str5",
"email_confirmation",
"confirmation_address",
"payment_method",
"subscription_type",
"billing_date",
"recurring_amount",
"frequency",
"cycles"
];
// 1. Iterate explicitly by the mandatory positional array sequence instead of the dictionary's internal order
foreach (string key in mandatorySequence)
{
if (string.IsNullOrEmpty(kvp.Value))
continue;
// Only append if the key exists in your source dictionary and contains data
if (data.TryGetValue(key, out string? rawValue) && !string.IsNullOrEmpty(rawValue))
{
// Payfast requires spaces to be '+' signs. HttpUtility does this natively.
string encodedVal = HttpUtility.UrlEncode(rawValue.Trim());
string key = kvp.Key;
// Payfast requires all OTHER percent-encoded hex arrays to be UPPERCASE (e.g., %3A instead of %3a)
string val = Regex.Replace(encodedVal, "%[0-9A-Fa-f]{2}", m => m.Value.ToUpperInvariant());
string encodedVal = HttpUtility.UrlEncode(kvp.Value.Trim());
string val = PercentEncodingRegex.Replace(encodedVal, m => m.Value.ToLowerInvariant());
pfOutput.Append($"{key}={val}&");
pfOutput.Append($"{key}={val}&");
}
}
string getString = pfOutput.Length > 0
@@ -168,12 +206,11 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
if (!string.IsNullOrWhiteSpace(passPhrase))
{
string encodedPassphrase = HttpUtility.UrlEncode(passPhrase.Trim());
string safePassphrase = PercentEncodingRegex.Replace(encodedPassphrase, m => m.Value.ToLowerInvariant());
string safePassphrase = Regex.Replace(encodedPassphrase, "%[0-9A-Fa-f]{2}", m => m.Value.ToUpperInvariant());
getString += $"&passphrase={safePassphrase}";
}
return HashService.ToMd5Hash(getString);
}
}
}
@@ -1,8 +1,6 @@
using LiteCharms.Features.Abstractions;
namespace LiteCharms.Features.Browser;
namespace LiteCharms.Features.Browser;
public sealed class LocalStorageService(ProtectedLocalStorage storage) : IService
public sealed class LocalStorageService(ProtectedLocalStorage storage)
{
public async ValueTask<Result> DeleteAsync(string key)
{