feat: implement SquadGateway (Nigeria - card, USSD, bank transfer, virtual accounts)#27
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds Squad (Nigeria) payment gateway: config + SquadConfig type, PaymentGatewayType enum entry, SquadGateway implementation (create/verify/refund), DI and factory registration, and PaymentService routing/selection updates for NGN and SQ_ references. ChangesSquad Payment Gateway
Sequence DiagramsequenceDiagram
participant Client
participant SquadGateway
participant SquadAPI as Squad API
Client->>SquadGateway: CreatePaymentAsync(PaymentRequest)
SquadGateway->>SquadAPI: POST /transaction/initiate (amount in kobo, metadata)
SquadAPI-->>SquadGateway: {data.checkout_url}
SquadGateway-->>Client: PaymentResponse(pending, checkout_url)
Client->>SquadGateway: VerifyPaymentAsync(transactionReference)
SquadGateway->>SquadAPI: GET /transaction/verify/{ref}
SquadAPI-->>SquadGateway: {transaction_status, transaction_amount, transaction_charge, created_at}
SquadGateway-->>Client: VerificationResponse(status, amounts, payment method)
Client->>SquadGateway: RefundPaymentAsync(RefundRequest)
SquadGateway->>SquadAPI: POST /transaction/refund (amount in kobo)
SquadAPI-->>SquadGateway: {data.refund_id}
SquadGateway-->>Client: RefundResponse(refunded, refund_id)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
Implements the Squad (GTCo) payment gateway integration for Nigeria (NGN), wiring it into the SDK’s gateway selection, reference-prefix detection, DI registration, and gateway factory creation flow.
Changes:
- Added a new
SquadGatewayimplementing create/verify/refund flows against Squad endpoints. - Updated gateway selection/detection to prefer Squad for NGN and recognize
SQ_references (plus broadened Flutterwave prefix detection). - Extended config, enum, DI registration, and factory initialization to include Squad.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| PayBridge.SDK/Services/PaymentService.cs | Prefers Squad for NGN and detects SQ_ references (plus FLW_). |
| PayBridge.SDK/Gateways/SquadGateway.cs | New gateway implementation for initiating, verifying, and refunding Squad transactions. |
| PayBridge.SDK/Factories/PaymentGatewayFactory.cs | Adds Squad to gateway initialization and makes the resolved gateway nullable. |
| PayBridge.SDK/Externsions/IServiceCollectionExtensions.cs | Registers SquadGateway in DI (enabled/all-gateways paths). |
| PayBridge.SDK/Enums/PaymentGatewayType.cs | Adds PaymentGatewayType.Squad = 7. |
| PayBridge.SDK/Dtos/PaymentGatewayConfig.cs | Adds SquadConfig (SecretKey/PublicKey/IsSandbox). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var txRef = $"SQ_{Guid.NewGuid():N}"; | ||
|
|
||
| var squadRequest = new | ||
| { | ||
| email = request.CustomerEmail, | ||
| amount = (int)(request.Amount * 100), // Squad expects amount in kobo | ||
| initiate_type = "inline", | ||
| currency = "NGN", | ||
| transaction_ref = txRef, | ||
| customer_name = request.CustomerName, |
| var errorMessage = root.TryGetProperty("message", out var msg) | ||
| ? msg.GetString() ?? "Refund failed" | ||
| : "Refund request failed"; | ||
|
|
||
| return new RefundResponse { Success = false, Message = errorMessage }; |
| services.AddScoped<StripeGateway>(); | ||
| services.AddScoped<PaystackGateway>(); | ||
| services.AddScoped<FlutterwaveGateway>(); | ||
| services.AddScoped<CheckoutGateway>(); | ||
| services.AddScoped<BenefitPayGateway>(); | ||
| services.AddScoped<KnetGateway>(); | ||
| services.AddScoped<SquadGateway>(); |
| case PaymentGatewayType.Squad: | ||
| services.AddScoped<SquadGateway>(); | ||
| break; |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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/Externsions/IServiceCollectionExtensions.cs`:
- Line 135: Register SquadGateway as an implementation of IPaymentGateway so it
is discoverable when PaymentService resolves IEnumerable<IPaymentGateway>;
update the registration in IServiceCollectionExtensions to add SquadGateway
under the IPaymentGateway service type (e.g., replace or add to the existing
services.AddScoped<SquadGateway>() with a registration that binds
IPaymentGateway to SquadGateway), and apply the same change for the other
occurrences referenced around the later block (lines showing similar
registrations) so PaymentService can auto-select Squad correctly.
In `@PayBridge.SDK/Gateways/SquadGateway.cs`:
- Line 60: Replace the unsafe direct cast of request.Amount to int with a safe
kobo conversion: add a helper method (e.g., ToKobo(decimal amount)) that rounds
using MidpointRounding.AwayFromZero and returns a checked long (to catch
overflow), then use ToKobo(request.Amount) wherever you currently cast (the
amount assignment at the line with "amount = (int)(request.Amount * 100)" and
the similar cast around line 202); ensure the payload/property types that
receive the value are updated to long if needed.
- Line 51: Redact sensitive data in SquadGateway logging: replace direct logging
of request.CustomerEmail in the _logger.LogInformation call with a non-PII
identifier (e.g., masked email or customer ID/hash) and stop logging full
provider responses; instead, in the methods that log provider responses (the
LogInformation/LogDebug calls around provider response handling), log only
non-sensitive metadata such as status codes, masked/hashed transaction IDs, and
a truncated/sanitized summary (no full payload or payment details). Locate the
_logger.LogInformation("Creating Squad payment for customer {Email}",
request.CustomerEmail) usage and any Log* calls that output provider responses
and update them to emit only non-PII fields as described.
- Around line 59-63: The Squad gateway is always sending currency "NGN" but
CreatePaymentAsync does not validate the incoming request currency; add a
boundary check in SquadGateway.CreatePaymentAsync to reject or fail fast if
request.Currency (or equivalent property) is not "NGN" (throw an
ArgumentException or return a failed Result/Response), and only proceed to build
the payload (including amount = (int)(request.Amount * 100)) when currency is
NGN; include a clear error message referencing Squad's NGN-only expectation so
callers know why the request was refused.
🪄 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: 02e1c55b-4872-4f5e-8768-8083bdb44c84
📒 Files selected for processing (6)
PayBridge.SDK/Dtos/PaymentGatewayConfig.csPayBridge.SDK/Enums/PaymentGatewayType.csPayBridge.SDK/Externsions/IServiceCollectionExtensions.csPayBridge.SDK/Factories/PaymentGatewayFactory.csPayBridge.SDK/Gateways/SquadGateway.csPayBridge.SDK/Services/PaymentService.cs
| services.AddScoped<CheckoutGateway>(); | ||
| services.AddScoped<BenefitPayGateway>(); | ||
| services.AddScoped<KnetGateway>(); | ||
| services.AddScoped<SquadGateway>(); |
There was a problem hiding this comment.
Register Squad under IPaymentGateway too, not only concrete type.
PaymentService resolves gateways from IEnumerable<IPaymentGateway> (PayBridge.SDK/Services/PaymentService.cs Line 23 and Line 31). With current registration, Squad is unavailable there, so Squad-specific calls fail as not configured and NGN auto-selection cannot actually pick Squad.
💡 Suggested fix
if (config.EnabledGateways.Count == 0)
{
services.AddScoped<StripeGateway>();
services.AddScoped<PaystackGateway>();
services.AddScoped<FlutterwaveGateway>();
services.AddScoped<CheckoutGateway>();
services.AddScoped<BenefitPayGateway>();
services.AddScoped<KnetGateway>();
services.AddScoped<SquadGateway>();
+ services.AddScoped<IPaymentGateway>(sp => sp.GetRequiredService<SquadGateway>());
return;
}
@@
case PaymentGatewayType.Squad:
services.AddScoped<SquadGateway>();
+ services.AddScoped<IPaymentGateway>(sp => sp.GetRequiredService<SquadGateway>());
break;Also applies to: 162-164
🤖 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/Externsions/IServiceCollectionExtensions.cs` at line 135,
Register SquadGateway as an implementation of IPaymentGateway so it is
discoverable when PaymentService resolves IEnumerable<IPaymentGateway>; update
the registration in IServiceCollectionExtensions to add SquadGateway under the
IPaymentGateway service type (e.g., replace or add to the existing
services.AddScoped<SquadGateway>() with a registration that binds
IPaymentGateway to SquadGateway), and apply the same change for the other
occurrences referenced around the later block (lines showing similar
registrations) so PaymentService can auto-select Squad correctly.
|
|
||
| public async Task<PaymentResponse> CreatePaymentAsync(PaymentRequest request) | ||
| { | ||
| _logger.LogInformation("Creating Squad payment for customer {Email}", request.CustomerEmail); |
There was a problem hiding this comment.
Reduce PII/sensitive payload logging in payment flows.
Line 51 logs customer email, and Lines 82/128/211 log full provider responses. These payloads can contain sensitive customer/payment data and should be redacted.
💡 Suggested fix
- _logger.LogInformation("Creating Squad payment for customer {Email}", request.CustomerEmail);
+ _logger.LogInformation("Creating Squad payment request");
- _logger.LogDebug("Squad create payment response: {Response}", responseBody);
+ _logger.LogDebug("Squad create payment response received. StatusCode: {StatusCode}", (int)response.StatusCode);
- _logger.LogDebug("Squad verify response: {Response}", responseBody);
+ _logger.LogDebug("Squad verify response received. StatusCode: {StatusCode}", (int)response.StatusCode);
- _logger.LogDebug("Squad refund response: {Response}", responseBody);
+ _logger.LogDebug("Squad refund response received. StatusCode: {StatusCode}", (int)response.StatusCode);Also applies to: 82-82, 128-128, 211-211
🤖 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/Gateways/SquadGateway.cs` at line 51, Redact sensitive data in
SquadGateway logging: replace direct logging of request.CustomerEmail in the
_logger.LogInformation call with a non-PII identifier (e.g., masked email or
customer ID/hash) and stop logging full provider responses; instead, in the
methods that log provider responses (the LogInformation/LogDebug calls around
provider response handling), log only non-sensitive metadata such as status
codes, masked/hashed transaction IDs, and a truncated/sanitized summary (no full
payload or payment details). Locate the _logger.LogInformation("Creating Squad
payment for customer {Email}", request.CustomerEmail) usage and any Log* calls
that output provider responses and update them to emit only non-PII fields as
described.
| email = request.CustomerEmail, | ||
| amount = (int)(request.Amount * 100), // Squad expects amount in kobo | ||
| initiate_type = "inline", | ||
| currency = "NGN", | ||
| transaction_ref = txRef, |
There was a problem hiding this comment.
Enforce NGN at the Squad gateway boundary.
CreatePaymentAsync always sends "NGN" to Squad but does not reject non-NGN requests. If Squad is explicitly selected with another currency, this can misstate transaction currency.
💡 Suggested fix
public async Task<PaymentResponse> CreatePaymentAsync(PaymentRequest request)
{
- _logger.LogInformation("Creating Squad payment for customer {Email}", request.CustomerEmail);
+ if (!string.Equals(request.Currency, "NGN", StringComparison.OrdinalIgnoreCase))
+ {
+ return new PaymentResponse
+ {
+ Success = false,
+ Status = PaymentStatus.Failed,
+ Message = $"Squad only supports NGN. Received: {request.Currency}"
+ };
+ }
try
{🤖 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/Gateways/SquadGateway.cs` around lines 59 - 63, The Squad
gateway is always sending currency "NGN" but CreatePaymentAsync does not
validate the incoming request currency; add a boundary check in
SquadGateway.CreatePaymentAsync to reject or fail fast if request.Currency (or
equivalent property) is not "NGN" (throw an ArgumentException or return a failed
Result/Response), and only proceed to build the payload (including amount =
(int)(request.Amount * 100)) when currency is NGN; include a clear error message
referencing Squad's NGN-only expectation so callers know why the request was
refused.
| var squadRequest = new | ||
| { | ||
| email = request.CustomerEmail, | ||
| amount = (int)(request.Amount * 100), // Squad expects amount in kobo |
There was a problem hiding this comment.
Use safe kobo conversion to avoid truncation/overflow.
Line 60 and Line 202 cast decimal amounts directly to int. That can truncate fractional values and overflow for larger amounts.
💡 Suggested fix
- amount = (int)(request.Amount * 100), // Squad expects amount in kobo
+ amount = ToKobo(request.Amount), // Squad expects amount in kobo
@@
- refund_amount = (int)(request.Amount * 100) // in kobo
+ refund_amount = ToKobo(request.Amount) // in koboprivate static long ToKobo(decimal amount)
{
return checked((long)decimal.Round(amount * 100m, 0, MidpointRounding.AwayFromZero));
}Also applies to: 202-202
🤖 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/Gateways/SquadGateway.cs` at line 60, Replace the unsafe direct
cast of request.Amount to int with a safe kobo conversion: add a helper method
(e.g., ToKobo(decimal amount)) that rounds using MidpointRounding.AwayFromZero
and returns a checked long (to catch overflow), then use ToKobo(request.Amount)
wherever you currently cast (the amount assignment at the line with "amount =
(int)(request.Amount * 100)" and the similar cast around line 202); ensure the
payload/property types that receive the value are updated to long if needed.
…rtual accounts) closes #10
3029442 to
12f4f65
Compare
Summary
Implements the Squad payment gateway by GTCo for Nigeria NGN payments.
Changes
PaymentGatewayType.cs— addedSquad = 7PaymentGatewayConfig.cs— addedSquadConfigwithSecretKey,PublicKey,IsSandboxGateways/SquadGateway.cs— full implementation: create, verify, refundIServiceCollectionExtensions.cs— registeredSquadGatewayin DIPaymentGatewayFactory.cs— added Squad to factory switch + nullable fixPaymentService.cs— NGN currency now prefers Squad first; addedSQ_prefix detectionHow it works
IsSandboxflag/transaction/initiate/transaction/verify/{ref}/transaction/refundConfig (appsettings.json)
Closes #10
Summary by CodeRabbit