Skip to content

Make automatic gateway routing deterministic and safe#121

Merged
teesofttech merged 3 commits into
masterfrom
fix/automatic-gateway-routing
Jul 18, 2026
Merged

Make automatic gateway routing deterministic and safe#121
teesofttech merged 3 commits into
masterfrom
fix/automatic-gateway-routing

Conversation

@teesofttech

@teesofttech teesofttech commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • honor DefaultGateway only when it is registered and compatible
  • remove registration-order and single-gateway routing bypasses
  • reject unknown currencies and unsupported cryptocurrency routes before provider calls
  • retain deterministic preferences for documented currency groups
  • add structured selection logs without customer or credential data
  • document routing rules and the remaining capability-metadata boundary

Root cause

Automatic routing ignored DefaultGateway, treated every unknown currency as a Stripe/Checkout currency, and fell back to the first DI-registered gateway when no preferred provider was available. This could invoke a provider that could not process the request.

Validation

  • routing suite covers compatible/incompatible defaults, registration order, single-gateway bypass, JPY, unknown currency, and unsupported method cases
  • full suite: 149 passed, 3 opt-in sandbox tests skipped
  • Release build: 0 warnings, 0 errors

Part of #65
Related to #90

Summary by CodeRabbit

  • New Features
    • Automatic gateway routing now uses only compatible gateways per currency (and supports deterministic priority), including normalized/trimmed currency matching.
    • Configured default gateways are honored only when compatible; otherwise routing follows deterministic order.
    • Crypto and unsupported payment methods/currencies are rejected before provider calls; unknown reference prefixes now require explicit gateway selection.
    • Saved payment methods are rejected until binding is implemented.
  • Documentation
    • Added guidance for automatic gateway routing rules and rejection conditions.
  • Tests
    • Expanded routing and selection test coverage (including deterministic behavior, currency normalization, and JPY documentation).
  • Bug Fixes
    • Improved configuration validation for enabled/default gateways with clearer failure messages.

Copilot AI review requested due to automatic review settings July 18, 2026 09:19
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PaymentService now validates automatic gateway routing by normalized currency and payment method, honors compatible configured defaults, rejects unsupported routes before provider calls, and rejects unknown transaction prefixes. AddPayBridge validates gateway configuration, with tests and documentation covering these rules.

Changes

Gateway configuration validation

Layer / File(s) Summary
Gateway configuration validation
PayBridge.SDK/Externsions/IServiceCollectionExtensions.cs, PayBridge.SDK.Test/Unit/IServiceCollectionExtensionsTests.cs
AddPayBridge validates default and explicitly enabled gateways, rejects Automatic in enabled gateways, and verifies required configuration and default-gateway membership.

Automatic gateway routing

Layer / File(s) Summary
Routing selection and rejection rules
PayBridge.SDK/Services/PaymentService.cs
Currency is normalized before selection; automatic routing supports cards, rejects unsupported methods and saved payment methods, selects compatible configured gateways deterministically, and rejects unknown reference prefixes.
Routing behavior tests and documentation
PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs, docs/automatic-gateway-routing.md
Tests and documentation cover default precedence, registration-order independence, unsupported routes, provider-call suppression, currency normalization, and Stripe JPY routing.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PaymentService
  participant ConfiguredGateways
  participant PaymentGateway
  PaymentService->>ConfiguredGateways: inspect normalized currency and payment method
  ConfiguredGateways-->>PaymentService: return compatible configured gateway
  PaymentService->>PaymentGateway: invoke selected provider
Loading

Possibly related issues

  • teesofttech/PayBridge#65 — Covers compatible default selection, deterministic routing, unsupported-route rejection, and related validation.
  • teesofttech/PayBridge#76 — Overlaps with the expanded PaymentService routing and lifecycle test coverage.

Possibly related PRs

Suggested labels: area: core, area: gateways, area: testing

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the core change: making automatic gateway routing deterministic and safer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/automatic-gateway-routing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs (1)

16-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for a compatible but unregistered default.

Configure Checkout as the USD default while registering only Stripe, then verify Stripe is selected. This directly protects the “default must be registered” contract.

Proposed test
+    [Fact]
+    public async Task Unregistered_compatible_default_is_skipped()
+    {
+        var stripe = Gateway(PaymentGatewayType.Stripe);
+        var service = CreateService(
+            new PaymentGatewayConfig { DefaultGateway = PaymentGatewayType.Checkout },
+            stripe.Object);
+
+        await service.CreatePaymentAsync(Request("USD"));
+
+        stripe.Verify(
+            item => item.CreatePaymentAsync(It.IsAny<PaymentRequest>()),
+            Times.Once);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs` around lines 16 - 59,
Extend Automatic_routing tests with a case configuring Checkout as the USD
default while registering only the Stripe gateway. Invoke CreatePaymentAsync
with a USD request, then verify Stripe.CreatePaymentAsync is called once and no
unregistered Checkout gateway is selected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs`:
- Around line 16-59: Extend Automatic_routing tests with a case configuring
Checkout as the USD default while registering only the Stripe gateway. Invoke
CreatePaymentAsync with a USD request, then verify Stripe.CreatePaymentAsync is
called once and no unregistered Checkout gateway is selected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc00fd1f-2f17-4bae-a491-9cc94c362178

📥 Commits

Reviewing files that changed from the base of the PR and between c23dd5d and 49249a6.

📒 Files selected for processing (3)
  • PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs
  • PayBridge.SDK/Services/PaymentService.cs
  • docs/automatic-gateway-routing.md

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens PayBridge’s automatic gateway routing to be deterministic and capability-aware, preventing “fallback to first registered gateway” behavior and making selection observable.

Changes:

  • Updates PaymentService routing to honor a compatible, registered DefaultGateway, otherwise select deterministically from a currency’s compatibility list and fail fast when no compatible gateway is configured.
  • Adds unit tests covering default gateway behavior, DI registration-order determinism, single-gateway bypass removal, unknown currency, crypto, and JPY routing.
  • Documents the automatic routing rules and current currency groupings.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
PayBridge.SDK/Services/PaymentService.cs Refactors automatic gateway selection to be deterministic, validate compatibility, log selection reasons, and fail fast instead of arbitrary fallback.
PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs Adds routing-focused unit tests to lock in deterministic behavior and pre-provider-call failures for unsupported routes.
docs/automatic-gateway-routing.md Documents routing rules, currency groups, DefaultGateway behavior, and explicit rejection of unsupported routes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 438 to 442
if (request.PaymentMethodType == PaymentMethodType.Crypto)
{
return _gateways.Keys.First();
throw new PaymentGatewayException(
"No configured gateway supports payment method Crypto.");
}
Comment on lines 451 to +455
// Select based on currency
switch (request.Currency?.ToUpper())
{
case "NGN":
return ChooseAvailableGateway(PaymentGatewayType.Monnify, PaymentGatewayType.Squad, PaymentGatewayType.Korapay, PaymentGatewayType.Interswitch, PaymentGatewayType.Remita, PaymentGatewayType.Opay, PaymentGatewayType.Paystack, PaymentGatewayType.Flutterwave);

case "KES":
case "GHS":
case "UGX":
case "TZS":
case "ZAR":
case "RWF":
case "ZMW":
case "CDF":
case "XOF":
case "XAF":
case "MWK":
return ChooseAvailableGateway(PaymentGatewayType.PeachPayments, PaymentGatewayType.PawaPay, PaymentGatewayType.DpoGroup, PaymentGatewayType.Flutterwave, PaymentGatewayType.Paystack);

case "BWP":
return ChooseAvailableGateway(PaymentGatewayType.PeachPayments, PaymentGatewayType.DpoGroup);

case "BHD":
return ChooseAvailableGateway(PaymentGatewayType.BenefitPay);

case "KWD":
return ChooseAvailableGateway(PaymentGatewayType.Knet);

case "USD":
case "EUR":
case "GBP":
default:
return ChooseAvailableGateway(PaymentGatewayType.Stripe, PaymentGatewayType.Checkout);
}
var compatibleGateways = request.Currency.ToUpperInvariant() switch
{
"NGN" => new[] { PaymentGatewayType.Monnify, PaymentGatewayType.Squad, PaymentGatewayType.Korapay, PaymentGatewayType.Interswitch, PaymentGatewayType.Remita, PaymentGatewayType.Opay, PaymentGatewayType.Paystack, PaymentGatewayType.Flutterwave },
"KES" or "GHS" or "UGX" or "TZS" or "ZAR" or "RWF" or "ZMW" or
Comment on lines +501 to +503
throw new PaymentGatewayException(
$"No configured gateway supports {request.Currency.ToUpperInvariant()} " +
$"with payment method {request.PaymentMethodType}.");

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@PayBridge.SDK/Services/PaymentService.cs`:
- Around line 48-54: Update ValidateRequest after NormalizeCurrency to reject a
currency that is null, empty, or whitespace-only, before payment-method routing
or provider selection continues. Preserve the normalized value for valid
currencies and raise the existing request-validation exception used by
PaymentService.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7dc3cf5f-ed50-4fab-b4f0-5c9932390f24

📥 Commits

Reviewing files that changed from the base of the PR and between 49249a6 and 0aaa1ba.

📒 Files selected for processing (5)
  • PayBridge.SDK.Test/Unit/IServiceCollectionExtensionsTests.cs
  • PayBridge.SDK.Test/Unit/PaymentServiceRoutingTests.cs
  • PayBridge.SDK/Externsions/IServiceCollectionExtensions.cs
  • PayBridge.SDK/Services/PaymentService.cs
  • docs/automatic-gateway-routing.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/automatic-gateway-routing.md

Comment on lines +48 to +54
request.Currency = NormalizeCurrency(request.Currency);

if (request.PaymentMethodType == PaymentMethodType.Crypto)
{
throw new PaymentGatewayException(
"No configured gateway supports payment method Crypto.");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject currency that becomes empty after normalization.

ValidateRequest accepts whitespace-only currency, then Line 48 converts it to "". Explicit gateway routing bypasses SelectBestGateway, so the malformed request reaches the provider.

Proposed fix
 request.Currency = NormalizeCurrency(request.Currency);
+if (request.Currency.Length == 0)
+{
+    throw new ArgumentException("Currency is required", nameof(request));
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
request.Currency = NormalizeCurrency(request.Currency);
if (request.PaymentMethodType == PaymentMethodType.Crypto)
{
throw new PaymentGatewayException(
"No configured gateway supports payment method Crypto.");
}
request.Currency = NormalizeCurrency(request.Currency);
if (request.Currency.Length == 0)
{
throw new ArgumentException("Currency is required", nameof(request));
}
if (request.PaymentMethodType == PaymentMethodType.Crypto)
{
throw new PaymentGatewayException(
"No configured gateway supports payment method Crypto.");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PayBridge.SDK/Services/PaymentService.cs` around lines 48 - 54, Update
ValidateRequest after NormalizeCurrency to reject a currency that is null,
empty, or whitespace-only, before payment-method routing or provider selection
continues. Preserve the normalized value for valid currencies and raise the
existing request-validation exception used by PaymentService.

@teesofttech
teesofttech merged commit 983939c into master Jul 18, 2026
7 checks passed
@teesofttech
teesofttech deleted the fix/automatic-gateway-routing branch July 18, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants