This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
using LiteCharms.Features.Abstractions;
|
using LiteCharms.Features.Abstractions;
|
||||||
|
using LiteCharms.Features.Browser;
|
||||||
using LiteCharms.Features.MidrandBooks.Abstractions;
|
using LiteCharms.Features.MidrandBooks.Abstractions;
|
||||||
|
|
||||||
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
namespace LiteCharms.Features.MidrandBooks.Extensions;
|
||||||
|
|
||||||
public static class Shop
|
public static class Shop
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddShopServices(this IServiceCollection services)
|
public static IServiceCollection AddShopServices(this IServiceCollection services, bool includeLocalStorage = false)
|
||||||
{
|
{
|
||||||
var serviceType = typeof(IService);
|
var serviceType = typeof(IService);
|
||||||
|
|
||||||
@@ -19,6 +20,9 @@ public static class Shop
|
|||||||
|
|
||||||
foreach (var coreImplementation in coreImplementations) services.AddScoped(coreImplementation);
|
foreach (var coreImplementation in coreImplementations) services.AddScoped(coreImplementation);
|
||||||
|
|
||||||
|
if (includeLocalStorage)
|
||||||
|
services.AddScoped<LocalStorageService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,8 +147,35 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
|
|||||||
{
|
{
|
||||||
var pfOutput = new StringBuilder();
|
var pfOutput = new StringBuilder();
|
||||||
|
|
||||||
// Define the exact structural sequence mandated by Payfast's documentation
|
var mandatorySequence = GetPayfastMandatoryFieldSequence();
|
||||||
string[] mandatorySequence =
|
|
||||||
|
foreach (string key in mandatorySequence)
|
||||||
|
{
|
||||||
|
if (data.TryGetValue(key, out string? rawValue) && !string.IsNullOrEmpty(rawValue))
|
||||||
|
{
|
||||||
|
string encodedVal = HttpUtility.UrlEncode(rawValue.Trim());
|
||||||
|
string val = PercentEncodingRegex.Replace(encodedVal, m => m.Value.ToUpperInvariant());
|
||||||
|
|
||||||
|
pfOutput.Append($"{key}={val}&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var getString = pfOutput.Length > 0
|
||||||
|
? pfOutput.ToString()[..^1]
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(passPhrase))
|
||||||
|
{
|
||||||
|
string encodedPassphrase = HttpUtility.UrlEncode(passPhrase.Trim());
|
||||||
|
string safePassphrase = PercentEncodingRegex.Replace(encodedPassphrase, m => m.Value.ToUpperInvariant());
|
||||||
|
|
||||||
|
getString += $"&passphrase={safePassphrase}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return HashService.ToMd5Hash(getString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetPayfastMandatoryFieldSequence() =>
|
||||||
[
|
[
|
||||||
"merchant_id",
|
"merchant_id",
|
||||||
"merchant_key",
|
"merchant_key",
|
||||||
@@ -182,35 +209,4 @@ public sealed partial class PayfastService(IDbContextFactory<MidrandBooksDbConte
|
|||||||
"frequency",
|
"frequency",
|
||||||
"cycles"
|
"cycles"
|
||||||
];
|
];
|
||||||
|
|
||||||
// 1. Iterate explicitly by the mandatory positional array sequence instead of the dictionary's internal order
|
|
||||||
foreach (string key in mandatorySequence)
|
|
||||||
{
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
pfOutput.Append($"{key}={val}&");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string getString = pfOutput.Length > 0
|
|
||||||
? pfOutput.ToString()[..^1]
|
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(passPhrase))
|
|
||||||
{
|
|
||||||
string encodedPassphrase = HttpUtility.UrlEncode(passPhrase.Trim());
|
|
||||||
string safePassphrase = Regex.Replace(encodedPassphrase, "%[0-9A-Fa-f]{2}", m => m.Value.ToUpperInvariant());
|
|
||||||
|
|
||||||
getString += $"&passphrase={safePassphrase}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return HashService.ToMd5Hash(getString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using LiteCharms.Features.Abstractions;
|
using LiteCharms.Features.Api.Configuration;
|
||||||
using LiteCharms.Features.Api.Configuration;
|
|
||||||
using LiteCharms.Features.Api.Models;
|
using LiteCharms.Features.Api.Models;
|
||||||
using LiteCharms.Features.Api.Sdk;
|
using LiteCharms.Features.Api.Sdk;
|
||||||
|
|
||||||
namespace LiteCharms.Features.Api;
|
namespace LiteCharms.Features.Api;
|
||||||
|
|
||||||
public sealed class TokenService(IConnectApi connectApi, IOptions<LiteCharmsClientSettings> clientOptions) : IService
|
public sealed class TokenService(IConnectApi connectApi, IOptions<LiteCharmsClientSettings> clientOptions)
|
||||||
{
|
{
|
||||||
private readonly LiteCharmsClientSettings clientSettings = clientOptions.Value;
|
private readonly LiteCharmsClientSettings clientSettings = clientOptions.Value;
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public static class Api
|
|||||||
options.Retry.BackoffType = Polly.DelayBackoffType.Exponential;
|
options.Retry.BackoffType = Polly.DelayBackoffType.Exponential;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddScoped<TokenService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user