Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions PayBridge.SDK.Test/Unit/IServiceCollectionExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,57 @@ public void AddPayBridge_WhenEnabledGatewaysContainsAutomatic_Throws()
.WithMessage("*cannot include Automatic*");
}

[Fact]
public void AddPayBridge_WhenEnabledGatewaysContainsDuplicateValues_Throws()
{
var configuration = BuildConfiguration(new Dictionary<string, string?>
{
["PaymentGatewayConfig:EnabledGateways:0"] = "Paystack",
["PaymentGatewayConfig:EnabledGateways:1"] = "Paystack",
["PaymentGatewayConfig:Paystack:SecretKey"] = "sk_test_paystack"
});
var services = new ServiceCollection();
services.AddLogging();

var action = () => services.AddPayBridge(configuration);

action.Should().Throw<Exception>()
.WithMessage("*appears more than once*");
}

[Fact]
public void AddPayBridge_WhenEnabledGatewayIsUndefinedEnumValue_Throws()
{
var configuration = BuildConfiguration(new Dictionary<string, string?>
{
["PaymentGatewayConfig:EnabledGateways:0"] = "999"
});
var services = new ServiceCollection();
services.AddLogging();

var action = () => services.AddPayBridge(configuration);

action.Should().Throw<Exception>()
.WithMessage("*not a defined PaymentGatewayType*");
}

[Fact]
public void AddPayBridge_WhenEnabledGatewayUsesPlaceholderCredential_Throws()
{
var configuration = BuildConfiguration(new Dictionary<string, string?>
{
["PaymentGatewayConfig:EnabledGateways:0"] = "Paystack",
["PaymentGatewayConfig:Paystack:SecretKey"] = "YOUR_PAYSTACK_SECRET_KEY"
});
var services = new ServiceCollection();
services.AddLogging();

var action = () => services.AddPayBridge(configuration);

action.Should().Throw<Exception>()
.WithMessage("*Paystack*missing required configuration values:*PaymentGatewayConfig:Paystack:SecretKey*");
}

[Fact]
public void AddPayBridge_WhenDefaultGatewayIsMissingRequiredConfig_Throws()
{
Expand Down
114 changes: 111 additions & 3 deletions PayBridge.SDK/Externsions/IServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,59 @@ private static void RegisterGateways(IServiceCollection services, PaymentGateway

private static void ValidateEnabledGatewayConfiguration(PaymentGatewayConfig config)
{
var errors = new List<string>();
var seenGateways = new HashSet<PaymentGatewayType>();

foreach (var gateway in config.EnabledGateways)
{
if (!Enum.IsDefined(typeof(PaymentGatewayType), gateway))
{
errors.Add(
$"Enabled gateway value '{(int)gateway}' is not a defined PaymentGatewayType.");
continue;
}

if (gateway == PaymentGatewayType.Automatic)
{
throw new PaymentConfigurationException(
errors.Add(
"PaymentGatewayConfig:EnabledGateways cannot include Automatic. " +
"Use concrete gateway types only.");
continue;
}

if (!seenGateways.Add(gateway))
{
errors.Add(
$"Enabled gateway '{gateway}' appears more than once. " +
"Remove duplicate entries.");
continue;
}

if (!IsGatewayConfigured(config, gateway))
{
throw new PaymentConfigurationException(
$"Enabled gateway '{gateway}' is missing required configuration values.");
var missingSettings = GetMissingGatewayConfigurationKeys(config, gateway);
errors.Add(
$"Enabled gateway '{gateway}' is missing required configuration values: {string.Join(", ", missingSettings)}.");
}
Comment on lines 209 to 214
}

if (errors.Count > 0)
{
throw new PaymentConfigurationException(
"Invalid PaymentGatewayConfig:EnabledGateways configuration:" +
Environment.NewLine +
string.Join(Environment.NewLine, errors.Select(error => $" - {error}")));
}
Comment on lines +217 to +223
}

private static void ValidateDefaultGatewayConfiguration(PaymentGatewayConfig config)
{
if (!Enum.IsDefined(typeof(PaymentGatewayType), config.DefaultGateway))
{
throw new PaymentConfigurationException(
$"Default gateway value '{(int)config.DefaultGateway}' is not a defined PaymentGatewayType.");
}
Comment on lines +228 to +232

if (config.DefaultGateway == PaymentGatewayType.Automatic)
{
return;
Expand Down Expand Up @@ -311,6 +345,80 @@ private static bool IsGatewayConfigured(PaymentGatewayConfig config, PaymentGate
};
}

private static IReadOnlyList<string> GetMissingGatewayConfigurationKeys(PaymentGatewayConfig config, PaymentGatewayType gateway)
{
var missingSettings = new List<string>();

void AddIfMissing(string key, string value)
{
if (!HasValue(value))
{
missingSettings.Add(key);
}
}

switch (gateway)
{
case PaymentGatewayType.Paystack:
AddIfMissing("PaymentGatewayConfig:Paystack:SecretKey", config.Paystack.SecretKey);
break;
case PaymentGatewayType.Flutterwave:
AddIfMissing("PaymentGatewayConfig:FlutterwaveConfig:SecretKey", config.FlutterwaveConfig.SecretKey);
break;
case PaymentGatewayType.Stripe:
AddIfMissing("PaymentGatewayConfig:Stripe:SecretKey", config.Stripe.SecretKey);
break;
case PaymentGatewayType.Checkout:
AddIfMissing("PaymentGatewayConfig:Checkout:SecretKey", config.Checkout.SecretKey);
break;
case PaymentGatewayType.BenefitPay:
AddIfMissing("PaymentGatewayConfig:BenefitPay:MerchantId", config.BenefitPay.MerchantId);
AddIfMissing("PaymentGatewayConfig:BenefitPay:ApiKey", config.BenefitPay.ApiKey);
break;
case PaymentGatewayType.Knet:
AddIfMissing("PaymentGatewayConfig:Knet:TransportId", config.Knet.TransportId);
AddIfMissing("PaymentGatewayConfig:Knet:Password", config.Knet.Password);
break;
case PaymentGatewayType.Monnify:
AddIfMissing("PaymentGatewayConfig:Monnify:ApiKey", config.Monnify.ApiKey);
AddIfMissing("PaymentGatewayConfig:Monnify:SecretKey", config.Monnify.SecretKey);
AddIfMissing("PaymentGatewayConfig:Monnify:ContractCode", config.Monnify.ContractCode);
break;
case PaymentGatewayType.Squad:
AddIfMissing("PaymentGatewayConfig:Squad:SecretKey", config.Squad.SecretKey);
break;
case PaymentGatewayType.Korapay:
AddIfMissing("PaymentGatewayConfig:Korapay:SecretKey", config.Korapay.SecretKey);
break;
case PaymentGatewayType.Interswitch:
AddIfMissing("PaymentGatewayConfig:Interswitch:ClientId", config.Interswitch.ClientId);
AddIfMissing("PaymentGatewayConfig:Interswitch:ClientSecret", config.Interswitch.ClientSecret);
AddIfMissing("PaymentGatewayConfig:Interswitch:MerchantCode", config.Interswitch.MerchantCode);
break;
case PaymentGatewayType.Remita:
AddIfMissing("PaymentGatewayConfig:Remita:MerchantId", config.Remita.MerchantId);
AddIfMissing("PaymentGatewayConfig:Remita:ServiceTypeId", config.Remita.ServiceTypeId);
AddIfMissing("PaymentGatewayConfig:Remita:ApiKey", config.Remita.ApiKey);
break;
case PaymentGatewayType.Opay:
AddIfMissing("PaymentGatewayConfig:Opay:MerchantId", config.Opay.MerchantId);
AddIfMissing("PaymentGatewayConfig:Opay:SecretKey", config.Opay.SecretKey);
break;
case PaymentGatewayType.DpoGroup:
AddIfMissing("PaymentGatewayConfig:DpoGroup:CompanyToken", config.DpoGroup.CompanyToken);
break;
case PaymentGatewayType.PawaPay:
AddIfMissing("PaymentGatewayConfig:PawaPay:ApiToken", config.PawaPay.ApiToken);
break;
case PaymentGatewayType.PeachPayments:
AddIfMissing("PaymentGatewayConfig:PeachPayments:EntityId", config.PeachPayments.EntityId);
AddIfMissing("PaymentGatewayConfig:PeachPayments:AccessToken", config.PeachPayments.AccessToken);
break;
}

return missingSettings;
}

private static bool HasValue(string value)
{
return !string.IsNullOrWhiteSpace(value) &&
Expand Down